@platformatic/basic 2.3.1 → 3.4.1
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/lib/base.js +21 -3
- package/lib/errors.js +1 -1
- package/lib/utils.js +9 -7
- package/lib/worker/child-manager.js +24 -9
- package/lib/worker/child-process.js +11 -2
- package/lib/worker/child-transport.js +5 -0
- package/lib/worker/listeners.js +7 -4
- package/package.json +5 -5
- package/schema.json +1 -1
package/lib/base.js
CHANGED
|
@@ -26,7 +26,7 @@ export class BaseStackable {
|
|
|
26
26
|
this.configManager = configManager
|
|
27
27
|
this.serverConfig = deepmerge(options.context.serverConfig ?? {}, configManager.current.server ?? {})
|
|
28
28
|
this.openapiSchema = null
|
|
29
|
-
this.
|
|
29
|
+
this.graphqlSchema = null
|
|
30
30
|
this.isEntrypoint = options.context.isEntrypoint
|
|
31
31
|
this.isProduction = options.context.isProduction
|
|
32
32
|
|
|
@@ -65,6 +65,10 @@ export class BaseStackable {
|
|
|
65
65
|
|
|
66
66
|
const enabled = config.watch?.enabled !== false
|
|
67
67
|
|
|
68
|
+
if (!enabled) {
|
|
69
|
+
return { enabled, path: this.root }
|
|
70
|
+
}
|
|
71
|
+
|
|
68
72
|
return {
|
|
69
73
|
enabled,
|
|
70
74
|
path: this.root,
|
|
@@ -125,7 +129,7 @@ export class BaseStackable {
|
|
|
125
129
|
}
|
|
126
130
|
}
|
|
127
131
|
|
|
128
|
-
async buildWithCommand (command, basePath, loader) {
|
|
132
|
+
async buildWithCommand (command, basePath, loader, scripts) {
|
|
129
133
|
if (Array.isArray(command)) {
|
|
130
134
|
command = command.join(' ')
|
|
131
135
|
}
|
|
@@ -135,12 +139,14 @@ export class BaseStackable {
|
|
|
135
139
|
this.#childManager = new ChildManager({
|
|
136
140
|
logger: this.logger,
|
|
137
141
|
loader,
|
|
142
|
+
scripts,
|
|
138
143
|
context: {
|
|
139
144
|
id: this.id,
|
|
140
145
|
// Always use URL to avoid serialization problem in Windows
|
|
141
146
|
root: pathToFileURL(this.root).toString(),
|
|
142
147
|
basePath,
|
|
143
148
|
logLevel: this.logger.level,
|
|
149
|
+
/* c8 ignore next 2 */
|
|
144
150
|
port: (this.isEntrypoint ? this.serverConfig?.port || 0 : undefined) ?? true,
|
|
145
151
|
host: (this.isEntrypoint ? this.serverConfig?.hostname : undefined) ?? true
|
|
146
152
|
}
|
|
@@ -158,10 +164,12 @@ export class BaseStackable {
|
|
|
158
164
|
})
|
|
159
165
|
|
|
160
166
|
// Route anything not catched by child process logger to the logger manually
|
|
167
|
+
/* c8 ignore next 3 */
|
|
161
168
|
subprocess.stdout.pipe(split2()).on('data', line => {
|
|
162
169
|
this.logger.info(line)
|
|
163
170
|
})
|
|
164
171
|
|
|
172
|
+
/* c8 ignore next 3 */
|
|
165
173
|
subprocess.stderr.pipe(split2()).on('data', line => {
|
|
166
174
|
this.logger.error(line)
|
|
167
175
|
})
|
|
@@ -191,6 +199,7 @@ export class BaseStackable {
|
|
|
191
199
|
root: pathToFileURL(this.root).toString(),
|
|
192
200
|
basePath,
|
|
193
201
|
logLevel: this.logger.level,
|
|
202
|
+
/* c8 ignore next 2 */
|
|
194
203
|
port: (this.isEntrypoint ? this.serverConfig?.port || 0 : undefined) ?? true,
|
|
195
204
|
host: (this.isEntrypoint ? this.serverConfig?.hostname : undefined) ?? true,
|
|
196
205
|
telemetry: this.telemetryConfig
|
|
@@ -207,10 +216,12 @@ export class BaseStackable {
|
|
|
207
216
|
this.subprocess = this.spawn(command)
|
|
208
217
|
|
|
209
218
|
// Route anything not catched by child process logger to the logger manually
|
|
219
|
+
/* c8 ignore next 3 */
|
|
210
220
|
this.subprocess.stdout.pipe(split2()).on('data', line => {
|
|
211
221
|
this.logger.info(line)
|
|
212
222
|
})
|
|
213
223
|
|
|
224
|
+
/* c8 ignore next 3 */
|
|
214
225
|
this.subprocess.stderr.pipe(split2()).on('data', line => {
|
|
215
226
|
this.logger.error(line)
|
|
216
227
|
})
|
|
@@ -223,14 +234,16 @@ export class BaseStackable {
|
|
|
223
234
|
|
|
224
235
|
this.#subprocessStarted = true
|
|
225
236
|
} catch (e) {
|
|
237
|
+
this.#childManager.close('SIGKILL')
|
|
226
238
|
throw new Error(`Cannot execute command "${command}": executable not found`)
|
|
227
239
|
} finally {
|
|
228
240
|
await this.#childManager.eject()
|
|
229
241
|
}
|
|
230
242
|
|
|
231
|
-
//
|
|
243
|
+
// If the process exits prematurely, terminate the thread with the same code
|
|
232
244
|
this.subprocess.on('exit', code => {
|
|
233
245
|
if (this.#subprocessStarted && typeof code === 'number' && code !== 0) {
|
|
246
|
+
this.#childManager.close('SIGKILL')
|
|
234
247
|
process.exit(code)
|
|
235
248
|
}
|
|
236
249
|
})
|
|
@@ -248,9 +261,14 @@ export class BaseStackable {
|
|
|
248
261
|
await exitPromise
|
|
249
262
|
}
|
|
250
263
|
|
|
264
|
+
getChildManager () {
|
|
265
|
+
return this.#childManager
|
|
266
|
+
}
|
|
267
|
+
|
|
251
268
|
spawn (command) {
|
|
252
269
|
const [executable, ...args] = parseCommandString(command)
|
|
253
270
|
|
|
271
|
+
/* c8 ignore next 3 */
|
|
254
272
|
return platform() === 'win32'
|
|
255
273
|
? spawn(command, { cwd: this.root, shell: true, windowsVerbatimArguments: true })
|
|
256
274
|
: spawn(executable, args, { cwd: this.root })
|
package/lib/errors.js
CHANGED
package/lib/utils.js
CHANGED
|
@@ -9,15 +9,15 @@ export function getServerUrl (server) {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export async function injectViaRequest (baseUrl, injectParams, onInject) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(injectParams.url, baseUrl).href
|
|
14
|
+
const requestParams = { method: injectParams.method, headers: injectParams.headers }
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if (injectParams.body) {
|
|
17
|
+
const body = injectParams.body
|
|
18
|
+
requestParams.body = typeof body === 'object' ? JSON.stringify(body) : body
|
|
19
|
+
}
|
|
19
20
|
|
|
20
|
-
try {
|
|
21
21
|
const { statusCode, headers, body } = await request(url, requestParams)
|
|
22
22
|
|
|
23
23
|
const rawPayload = Buffer.from(await body.arrayBuffer())
|
|
@@ -53,11 +53,13 @@ export function ensureFileUrl (pathOrUrl) {
|
|
|
53
53
|
return pathToFileURL(pathOrUrl)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/* c8 ignore next 4 */
|
|
56
57
|
// This is to avoid common path/URL problems on Windows
|
|
57
58
|
export function importFile (path) {
|
|
58
59
|
return import(ensureFileUrl(path))
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/* c8 ignore next 6 */
|
|
61
63
|
export function resolvePackage (root, pkg) {
|
|
62
64
|
const require = createRequire(root)
|
|
63
65
|
|
|
@@ -11,17 +11,18 @@ 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
|
-
|
|
15
14
|
export const isWindows = platform() === 'win32'
|
|
16
15
|
|
|
17
16
|
// In theory we could use the context.id to namespace even more, but due to
|
|
18
17
|
// UNIX socket length limitation on MacOS, we don't.
|
|
19
|
-
function generateChildrenId (context) {
|
|
18
|
+
export function generateChildrenId (context) {
|
|
20
19
|
return [process.pid, Date.now()].join('-')
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
export function getSocketPath (id) {
|
|
24
23
|
let socketPath = null
|
|
24
|
+
|
|
25
|
+
/* c8 ignore next 7 */
|
|
25
26
|
if (platform() === 'win32') {
|
|
26
27
|
socketPath = `\\\\.\\pipe\\plt-${id}`
|
|
27
28
|
} else {
|
|
@@ -59,7 +60,9 @@ export class ChildManager extends ITC {
|
|
|
59
60
|
...itcOpts,
|
|
60
61
|
handlers: {
|
|
61
62
|
log: message => {
|
|
62
|
-
|
|
63
|
+
/* c8 ignore next */
|
|
64
|
+
const logs = Array.isArray(message.logs) ? message.logs : [message.logs]
|
|
65
|
+
this._forwardLogs(logs)
|
|
63
66
|
},
|
|
64
67
|
fetch: request => {
|
|
65
68
|
return this.#fetch(request)
|
|
@@ -106,6 +109,7 @@ export class ChildManager extends ITC {
|
|
|
106
109
|
this.#clients.delete(ws)
|
|
107
110
|
})
|
|
108
111
|
|
|
112
|
+
/* c8 ignore next 7 */
|
|
109
113
|
ws.on('error', error => {
|
|
110
114
|
this.#handleUnexpectedError(
|
|
111
115
|
error,
|
|
@@ -121,13 +125,16 @@ export class ChildManager extends ITC {
|
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
async close (signal) {
|
|
124
|
-
|
|
128
|
+
if (this.#dataPath) {
|
|
129
|
+
await rm(this.#dataPath, { force: true })
|
|
130
|
+
}
|
|
125
131
|
|
|
126
132
|
for (const client of this.#clients) {
|
|
127
133
|
this.#currentClient = client
|
|
128
134
|
this._send(generateNotification('close', signal))
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
this.#server?.close()
|
|
131
138
|
super.close()
|
|
132
139
|
}
|
|
133
140
|
|
|
@@ -163,16 +170,24 @@ export class ChildManager extends ITC {
|
|
|
163
170
|
process.env.PLT_MANAGER_ID = ''
|
|
164
171
|
}
|
|
165
172
|
|
|
173
|
+
getSocketPath () {
|
|
174
|
+
return this.#socketPath
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getClients () {
|
|
178
|
+
return this.#clients
|
|
179
|
+
}
|
|
180
|
+
|
|
166
181
|
register () {
|
|
167
182
|
register(this.#loader, { data: this.#context })
|
|
168
183
|
}
|
|
169
184
|
|
|
170
185
|
send (client, name, message) {
|
|
171
186
|
this.#currentClient = client
|
|
172
|
-
super.send(name, message)
|
|
187
|
+
return super.send(name, message)
|
|
173
188
|
}
|
|
174
189
|
|
|
175
|
-
_send (message) {
|
|
190
|
+
_send (message, stringify = true) {
|
|
176
191
|
if (!this.#currentClient) {
|
|
177
192
|
this.#currentClient = this.#requests.get(message.reqId)
|
|
178
193
|
this.#requests.delete(message.reqId)
|
|
@@ -182,7 +197,7 @@ export class ChildManager extends ITC {
|
|
|
182
197
|
}
|
|
183
198
|
}
|
|
184
199
|
|
|
185
|
-
this.#currentClient.send(JSON.stringify(message))
|
|
200
|
+
this.#currentClient.send(stringify ? JSON.stringify(message) : message)
|
|
186
201
|
this.#currentClient = null
|
|
187
202
|
}
|
|
188
203
|
|
|
@@ -198,8 +213,8 @@ export class ChildManager extends ITC {
|
|
|
198
213
|
this.#server.close()
|
|
199
214
|
}
|
|
200
215
|
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
/* c8 ignore next 3 */
|
|
217
|
+
_forwardLogs (logs) {
|
|
203
218
|
workerData.loggingPort.postMessage({ logs: logs.map(m => JSON.stringify(m)) })
|
|
204
219
|
}
|
|
205
220
|
|
|
@@ -86,6 +86,7 @@ export class ChildProcess extends ITC {
|
|
|
86
86
|
constructor () {
|
|
87
87
|
super({ throwOnMissingHandler: false, name: `${process.env.PLT_MANAGER_ID}-child-process` })
|
|
88
88
|
|
|
89
|
+
/* c8 ignore next */
|
|
89
90
|
const protocol = platform() === 'win32' ? 'ws+unix:' : 'ws+unix://'
|
|
90
91
|
this.#socket = new WebSocket(`${protocol}${getSocketPath(process.env.PLT_MANAGER_ID)}`)
|
|
91
92
|
this.#pendingMessages = []
|
|
@@ -109,6 +110,7 @@ export class ChildProcess extends ITC {
|
|
|
109
110
|
// Never hang the process on this socket.
|
|
110
111
|
this.#socket._socket.unref()
|
|
111
112
|
|
|
113
|
+
/* c8 ignore next 3 */
|
|
112
114
|
for (const message of this.#pendingMessages) {
|
|
113
115
|
this.#socket.send(message)
|
|
114
116
|
}
|
|
@@ -123,6 +125,7 @@ export class ChildProcess extends ITC {
|
|
|
123
125
|
}
|
|
124
126
|
})
|
|
125
127
|
|
|
128
|
+
/* c8 ignore next 5 */
|
|
126
129
|
this.#socket.on('error', error => {
|
|
127
130
|
process._rawDebug(error)
|
|
128
131
|
// There is nothing to log here as the connection with the parent thread is lost. Exit with a special code
|
|
@@ -131,6 +134,7 @@ export class ChildProcess extends ITC {
|
|
|
131
134
|
}
|
|
132
135
|
|
|
133
136
|
_send (message) {
|
|
137
|
+
/* c8 ignore next 4 */
|
|
134
138
|
if (this.#socket.readyState === WebSocket.CONNECTING) {
|
|
135
139
|
this.#pendingMessages.push(JSON.stringify(message))
|
|
136
140
|
return
|
|
@@ -143,6 +147,7 @@ export class ChildProcess extends ITC {
|
|
|
143
147
|
return once(this.#socket, 'close')
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
/* c8 ignore next 3 */
|
|
146
151
|
_close () {
|
|
147
152
|
this.#socket.close()
|
|
148
153
|
}
|
|
@@ -167,6 +172,7 @@ export class ChildProcess extends ITC {
|
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
|
|
175
|
+
/* c8 ignore next 5 */
|
|
170
176
|
#setupTelemetry () {
|
|
171
177
|
if (globalThis.platformatic.telemetry) {
|
|
172
178
|
setupNodeHTTPTelemetry(globalThis.platformatic.telemetry, this.#logger)
|
|
@@ -202,11 +208,12 @@ export class ChildProcess extends ITC {
|
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
const { family, address: host, port } = address
|
|
211
|
+
/* c8 ignore next */
|
|
205
212
|
const url = new URL(family === 'IPv6' ? `http://[${host}]:${port}` : `http://${host}:${port}`).origin
|
|
206
213
|
|
|
207
214
|
this.notify('url', url)
|
|
208
215
|
},
|
|
209
|
-
error: error => {
|
|
216
|
+
error: ({ error }) => {
|
|
210
217
|
tracingChannel('net.server.listen').unsubscribe(subscribers)
|
|
211
218
|
this.notify('error', error)
|
|
212
219
|
}
|
|
@@ -226,7 +233,8 @@ export class ChildProcess extends ITC {
|
|
|
226
233
|
`Child process for service ${globalThis.platformatic.id} threw an ${type}.`
|
|
227
234
|
)
|
|
228
235
|
|
|
229
|
-
|
|
236
|
+
// Give some time to the logger and ITC notifications to land before shutting down
|
|
237
|
+
setTimeout(() => process.exit(exitCodes.PROCESS_UNHANDLED_ERROR), 100)
|
|
230
238
|
}
|
|
231
239
|
|
|
232
240
|
process.on('uncaughtException', handleUnhandled.bind(this, 'uncaught exception'))
|
|
@@ -255,6 +263,7 @@ async function main () {
|
|
|
255
263
|
globalThis[Symbol.for('plt.children.itc')] = new ChildProcess()
|
|
256
264
|
}
|
|
257
265
|
|
|
266
|
+
/* c8 ignore next 3 */
|
|
258
267
|
if (!isWindows || basename(process.argv.at(-1)) !== 'npm-prefix.js') {
|
|
259
268
|
await main()
|
|
260
269
|
}
|
|
@@ -7,12 +7,14 @@ import build from 'pino-abstract-transport'
|
|
|
7
7
|
import { WebSocket } from 'ws'
|
|
8
8
|
import { getSocketPath } from './child-manager.js'
|
|
9
9
|
|
|
10
|
+
/* c8 ignore next 5 */
|
|
10
11
|
function logDirectError (message, error) {
|
|
11
12
|
process._rawDebug(`Logger thread for child process of service ${workerData.id} ${message}.`, {
|
|
12
13
|
error: ensureLoggableError(error)
|
|
13
14
|
})
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
/* c8 ignore next 4 */
|
|
16
18
|
function handleUnhandled (type, error) {
|
|
17
19
|
logDirectError(`threw an ${type}`, error)
|
|
18
20
|
process.exit(6)
|
|
@@ -23,6 +25,7 @@ process.on('unhandledRejection', handleUnhandled.bind(null, 'unhandled rejection
|
|
|
23
25
|
|
|
24
26
|
export default async function (opts) {
|
|
25
27
|
try {
|
|
28
|
+
/* c8 ignore next */
|
|
26
29
|
const protocol = platform() === 'win32' ? 'ws+unix:' : 'ws+unix://'
|
|
27
30
|
const socket = new WebSocket(`${protocol}${getSocketPath(process.env.PLT_MANAGER_ID)}`)
|
|
28
31
|
|
|
@@ -31,6 +34,7 @@ export default async function (opts) {
|
|
|
31
34
|
// Do not process responses but empty the socket inbound queue
|
|
32
35
|
socket.on('message', () => {})
|
|
33
36
|
|
|
37
|
+
/* c8 ignore next 3 */
|
|
34
38
|
socket.on('error', error => {
|
|
35
39
|
logDirectError('threw a socket error', error)
|
|
36
40
|
})
|
|
@@ -48,6 +52,7 @@ export default async function (opts) {
|
|
|
48
52
|
}
|
|
49
53
|
}
|
|
50
54
|
)
|
|
55
|
+
/* c8 ignore next 3 */
|
|
51
56
|
} catch (error) {
|
|
52
57
|
logDirectError('threw a connection error', error)
|
|
53
58
|
}
|
package/lib/worker/listeners.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { withResolvers } from '@platformatic/utils'
|
|
2
2
|
import { subscribe, tracingChannel, unsubscribe } from 'node:diagnostics_channel'
|
|
3
3
|
|
|
4
|
-
export function createServerListener (overridePort = true, overrideHost
|
|
4
|
+
export function createServerListener (overridePort = true, overrideHost) {
|
|
5
5
|
const { promise, resolve, reject } = withResolvers()
|
|
6
6
|
|
|
7
7
|
const subscribers = {
|
|
@@ -19,9 +19,10 @@ export function createServerListener (overridePort = true, overrideHost = true)
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
asyncEnd ({ server }) {
|
|
22
|
+
cancel()
|
|
22
23
|
resolve(server)
|
|
23
24
|
},
|
|
24
|
-
error (error) {
|
|
25
|
+
error ({ error }) {
|
|
25
26
|
cancel()
|
|
26
27
|
reject(error)
|
|
27
28
|
}
|
|
@@ -32,8 +33,10 @@ export function createServerListener (overridePort = true, overrideHost = true)
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
tracingChannel('net.server.listen').subscribe(subscribers)
|
|
35
|
-
promise.
|
|
36
|
-
|
|
36
|
+
promise.cancel = function () {
|
|
37
|
+
cancel()
|
|
38
|
+
resolve(null)
|
|
39
|
+
}
|
|
37
40
|
|
|
38
41
|
return promise
|
|
39
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/basic",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.4.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"split2": "^4.2.0",
|
|
24
24
|
"undici": "^6.19.5",
|
|
25
25
|
"ws": "^8.18.0",
|
|
26
|
-
"@platformatic/
|
|
27
|
-
"@platformatic/
|
|
28
|
-
"@platformatic/
|
|
29
|
-
"@platformatic/
|
|
26
|
+
"@platformatic/config": "3.4.1",
|
|
27
|
+
"@platformatic/itc": "3.4.1",
|
|
28
|
+
"@platformatic/telemetry": "3.4.1",
|
|
29
|
+
"@platformatic/utils": "3.4.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"borp": "^0.17.0",
|
package/schema.json
CHANGED