@platformatic/basic 2.67.0-alpha.1 → 2.67.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/index.js CHANGED
@@ -28,10 +28,6 @@ export const configCandidates = [
28
28
  'watt.tml'
29
29
  ]
30
30
 
31
- function hasDependency (packageJson, dependency) {
32
- return packageJson.dependencies?.[dependency] || packageJson.devDependencies?.[dependency]
33
- }
34
-
35
31
  function isImportFailedError (error, pkg) {
36
32
  if (error.code !== 'ERR_MODULE_NOT_FOUND' && error.code !== 'MODULE_NOT_FOUND') {
37
33
  return false
@@ -64,37 +60,15 @@ async function importStackablePackage (directory, pkg) {
64
60
 
65
61
  const serviceDirectory = workerData ? relative(workerData.dirname, directory) : directory
66
62
  throw new Error(
67
- `Unable to import package '${pkg}'. Please add it as a dependency in the package.json file in the folder ${serviceDirectory}.`
63
+ `Unable to import package '${pkg}'. Please add it as a dependency in the package.json file in the folder ${serviceDirectory}.`
68
64
  )
69
65
  }
70
66
  }
71
67
 
72
- export function detectStackable (packageJson) {
73
- let name = '@platformatic/node'
74
- let label = 'Node.js'
75
-
76
- if (hasDependency(packageJson, '@nestjs/core')) {
77
- name = '@platformatic/nest'
78
- label = 'NestJS'
79
- } else if (hasDependency(packageJson, 'next')) {
80
- name = '@platformatic/next'
81
- label = 'Next.js'
82
- } else if (hasDependency(packageJson, '@remix-run/dev')) {
83
- name = '@platformatic/remix'
84
- label = 'Remix'
85
- } else if (hasDependency(packageJson, 'astro')) {
86
- name = '@platformatic/astro'
87
- label = 'Astro'
88
- // Since Vite is often used with other frameworks, we must check for Vite last
89
- } else if (hasDependency(packageJson, 'vite')) {
90
- name = '@platformatic/vite'
91
- label = 'Vite'
92
- }
93
-
94
- return { name, label }
95
- }
96
-
97
68
  export async function importStackableAndConfig (root, config) {
69
+ let moduleName = '@platformatic/node'
70
+ let autodetectDescription = 'is using a generic Node.js application'
71
+
98
72
  let rootPackageJson
99
73
  try {
100
74
  rootPackageJson = JSON.parse(await readFile(resolve(root, 'package.json'), 'utf-8'))
@@ -113,16 +87,25 @@ export async function importStackableAndConfig (root, config) {
113
87
  }
114
88
  }
115
89
 
116
- const { label, name: moduleName } = detectStackable(rootPackageJson)
90
+ const { dependencies, devDependencies } = rootPackageJson
91
+
92
+ if (dependencies?.next || devDependencies?.next) {
93
+ autodetectDescription = 'is using Next.js'
94
+ moduleName = '@platformatic/next'
95
+ } else if (dependencies?.['@remix-run/dev'] || devDependencies?.['@remix-run/dev']) {
96
+ autodetectDescription = 'is using Remix'
97
+ moduleName = '@platformatic/remix'
98
+ } else if (dependencies?.astro || devDependencies?.astro) {
99
+ autodetectDescription = 'is using Astro'
100
+ moduleName = '@platformatic/astro'
101
+ } else if (dependencies?.vite || devDependencies?.vite) {
102
+ autodetectDescription = 'is using Vite'
103
+ moduleName = '@platformatic/vite'
104
+ }
105
+
117
106
  const stackable = await importStackablePackage(root, moduleName)
118
107
 
119
- return {
120
- stackable,
121
- config,
122
- autodetectDescription:
123
- moduleName === '@platformatic/node' ? 'is a generic Node.js application' : `is using ${label}`,
124
- moduleName
125
- }
108
+ return { stackable, config, autodetectDescription, moduleName }
126
109
  }
127
110
 
128
111
  async function buildStackable (opts) {
package/lib/base.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { client, collectMetrics } from '@platformatic/metrics'
2
- import { buildPinoOptions, deepmerge, executeWithTimeout } from '@platformatic/utils'
2
+ import { deepmerge, executeWithTimeout, buildPinoOptions } from '@platformatic/utils'
3
3
  import { parseCommandString } from 'execa'
4
4
  import { spawn } from 'node:child_process'
5
- import EventEmitter, { once } from 'node:events'
5
+ import { once } from 'node:events'
6
6
  import { existsSync } from 'node:fs'
7
7
  import { platform } from 'node:os'
8
8
  import { pathToFileURL } from 'node:url'
@@ -14,17 +14,13 @@ import { ChildManager } from './worker/child-manager.js'
14
14
 
15
15
  const kITC = Symbol.for('plt.runtime.itc')
16
16
 
17
- export class BaseStackable extends EventEmitter {
17
+ export class BaseStackable {
18
18
  childManager
19
19
  subprocess
20
- subprocessForceClose
21
- subprocessTerminationSignal
22
20
  #subprocessStarted
23
21
  #metricsCollected
24
22
 
25
23
  constructor (type, version, options, root, configManager, standardStreams = {}) {
26
- super()
27
-
28
24
  options.context.worker ??= { count: 1, index: 0 }
29
25
 
30
26
  this.type = type
@@ -52,18 +48,9 @@ export class BaseStackable extends EventEmitter {
52
48
  this.runtimeConfig = deepmerge(options.context?.runtimeConfig ?? {}, workerData?.config ?? {})
53
49
  this.stdout = standardStreams?.stdout ?? process.stdout
54
50
  this.stderr = standardStreams?.stderr ?? process.stderr
55
- this.subprocessForceClose = false
56
- this.subprocessTerminationSignal = 'SIGINT'
57
51
 
58
52
  const loggerOptions = deepmerge(this.runtimeConfig?.logger ?? {}, this.configManager.current?.logger ?? {})
59
- const pinoOptions = buildPinoOptions(
60
- loggerOptions,
61
- this.serverConfig?.logger,
62
- this.serviceId,
63
- this.workerId,
64
- options,
65
- this.root
66
- )
53
+ const pinoOptions = buildPinoOptions(loggerOptions, this.serverConfig?.logger, this.serviceId, this.workerId, options, this.root)
67
54
  this.logger = pino(pinoOptions, standardStreams?.stdout)
68
55
 
69
56
  // Setup globals
@@ -82,7 +69,6 @@ export class BaseStackable extends EventEmitter {
82
69
  prometheus: { client, registry: this.metricsRegistry },
83
70
  setCustomHealthCheck: this.setCustomHealthCheck.bind(this),
84
71
  setCustomReadinessCheck: this.setCustomReadinessCheck.bind(this),
85
- notifyConfig: this.notifyConfig.bind(this),
86
72
  logger: this.logger
87
73
  })
88
74
  }
@@ -128,22 +114,6 @@ export class BaseStackable extends EventEmitter {
128
114
  return this.getUrl() ?? this.getDispatchFunc()
129
115
  }
130
116
 
131
- getMeta () {
132
- return {
133
- composer: {
134
- wantsAbsoluteUrls: false
135
- }
136
- }
137
- }
138
-
139
- async getMetrics ({ format } = {}) {
140
- if (this.childManager && this.clientWs) {
141
- return this.childManager.send(this.clientWs, 'getMetrics', { format })
142
- }
143
-
144
- return format === 'json' ? await this.metricsRegistry.getMetricsAsJSON() : await this.metricsRegistry.metrics()
145
- }
146
-
147
117
  async getOpenapiSchema () {
148
118
  return this.openapiSchema
149
119
  }
@@ -257,7 +227,6 @@ export class BaseStackable extends EventEmitter {
257
227
 
258
228
  this.childManager.on('config', config => {
259
229
  this.subprocessConfig = config
260
- this.notifyConfig(config)
261
230
  })
262
231
 
263
232
  this.childManager.on('connectionString', connectionString => {
@@ -309,18 +278,13 @@ export class BaseStackable extends EventEmitter {
309
278
  const exitPromise = once(this.subprocess, 'exit')
310
279
 
311
280
  // Attempt graceful close on the process
312
- const handled = await this.childManager.send(this.clientWs, 'close', this.subprocessTerminationSignal)
313
-
314
- if (!handled && this.subprocessForceClose) {
315
- this.subprocess.kill(this.subprocessTerminationSignal)
316
- }
281
+ this.childManager.notify(this.clientWs, 'close')
317
282
 
318
283
  // If the process hasn't exited in X seconds, kill it in the polite way
319
284
  /* c8 ignore next 10 */
320
285
  const res = await executeWithTimeout(exitPromise, exitTimeout)
321
-
322
286
  if (res === 'timeout') {
323
- this.subprocess.kill(this.subprocessTerminationSignal)
287
+ this.subprocess.kill(this.subprocessTerminationSignal ?? 'SIGINT')
324
288
 
325
289
  // If the process hasn't exited in X seconds, kill it the hard way
326
290
  const res = await executeWithTimeout(exitPromise, exitTimeout)
@@ -339,28 +303,6 @@ export class BaseStackable extends EventEmitter {
339
303
  return this.childManager
340
304
  }
341
305
 
342
- async getChildManagerContext (basePath) {
343
- const meta = await this.getMeta()
344
-
345
- return {
346
- id: this.id,
347
- config: this.configManager.current,
348
- serviceId: this.serviceId,
349
- workerId: this.workerId,
350
- // Always use URL to avoid serialization problem in Windows
351
- root: pathToFileURL(this.root).toString(),
352
- basePath,
353
- logLevel: this.logger.level,
354
- isEntrypoint: this.isEntrypoint,
355
- runtimeBasePath: this.runtimeConfig?.basePath ?? null,
356
- wantsAbsoluteUrls: meta.composer?.wantsAbsoluteUrls ?? false,
357
- /* c8 ignore next 2 - Else branches */
358
- port: (this.isEntrypoint ? this.serverConfig?.port || 0 : undefined) ?? true,
359
- host: (this.isEntrypoint ? this.serverConfig?.hostname : undefined) ?? true,
360
- telemetryConfig: this.telemetryConfig
361
- }
362
- }
363
-
364
306
  async spawn (command) {
365
307
  const [executable, ...args] = parseCommandString(command)
366
308
  const hasChainedCommands = command.includes('&&') || command.includes('||') || command.includes(';')
@@ -386,10 +328,6 @@ export class BaseStackable extends EventEmitter {
386
328
  return subprocess
387
329
  }
388
330
 
389
- notifyConfig (config) {
390
- this.emit('config', config)
391
- }
392
-
393
331
  async _collectMetrics () {
394
332
  if (this.#metricsCollected) {
395
333
  return
@@ -453,7 +391,45 @@ export class BaseStackable extends EventEmitter {
453
391
  }
454
392
  }
455
393
 
394
+ async getMetrics ({ format } = {}) {
395
+ if (this.childManager && this.clientWs) {
396
+ return this.childManager.send(this.clientWs, 'getMetrics', { format })
397
+ }
398
+
399
+ return format === 'json' ? await this.metricsRegistry.getMetricsAsJSON() : await this.metricsRegistry.metrics()
400
+ }
401
+
456
402
  async #invalidateHttpCache (opts = {}) {
457
403
  await globalThis[kITC].send('invalidateHttpCache', opts)
458
404
  }
405
+
406
+ getMeta () {
407
+ return {
408
+ composer: {
409
+ wantsAbsoluteUrls: false
410
+ }
411
+ }
412
+ }
413
+
414
+ async getChildManagerContext (basePath) {
415
+ const meta = await this.getMeta()
416
+
417
+ return {
418
+ id: this.id,
419
+ config: this.configManager.current,
420
+ serviceId: this.serviceId,
421
+ workerId: this.workerId,
422
+ // Always use URL to avoid serialization problem in Windows
423
+ root: pathToFileURL(this.root).toString(),
424
+ basePath,
425
+ logLevel: this.logger.level,
426
+ isEntrypoint: this.isEntrypoint,
427
+ runtimeBasePath: this.runtimeConfig?.basePath ?? null,
428
+ wantsAbsoluteUrls: meta.composer?.wantsAbsoluteUrls ?? false,
429
+ /* c8 ignore next 2 */
430
+ port: (this.isEntrypoint ? this.serverConfig?.port || 0 : undefined) ?? true,
431
+ host: (this.isEntrypoint ? this.serverConfig?.hostname : undefined) ?? true,
432
+ telemetryConfig: this.telemetryConfig
433
+ }
434
+ }
459
435
  }
@@ -1,13 +1,6 @@
1
1
  import { ITC } from '@platformatic/itc'
2
2
  import { client, collectMetrics } from '@platformatic/metrics'
3
- import {
4
- buildPinoFormatters,
5
- buildPinoTimestamp,
6
- disablePinoDirectWrite,
7
- ensureFlushedWorkerStdio,
8
- ensureLoggableError,
9
- features
10
- } from '@platformatic/utils'
3
+ import { buildPinoFormatters, buildPinoTimestamp, disablePinoDirectWrite, ensureFlushedWorkerStdio, ensureLoggableError, features } from '@platformatic/utils'
11
4
  import diagnosticChannel, { tracingChannel } from 'node:diagnostics_channel'
12
5
  import { EventEmitter, once } from 'node:events'
13
6
  import { readFile } from 'node:fs/promises'
@@ -68,6 +61,7 @@ function createInterceptor () {
68
61
  export class ChildProcess extends ITC {
69
62
  #listener
70
63
  #socket
64
+ #child
71
65
  #logger
72
66
  #metricsRegistry
73
67
  #pendingMessages
@@ -82,25 +76,6 @@ export class ChildProcess extends ITC {
82
76
  },
83
77
  getMetrics: (...args) => {
84
78
  return this.#getMetrics(...args)
85
- },
86
- close: (signal) => {
87
- let handled = false
88
-
89
- try {
90
- handled = globalThis.platformatic.events.emit('close', signal)
91
- } catch (error) {
92
- this.#logger.error({ err: ensureLoggableError(error) }, 'Error while handling close event.')
93
- process.exitCode = 1
94
- }
95
-
96
- if (!handled) {
97
- // No user event, just exit without errors
98
- setImmediate(() => {
99
- process.exit(process.exitCode ?? 0)
100
- })
101
- }
102
-
103
- return handled
104
79
  }
105
80
  }
106
81
  })
@@ -117,37 +92,23 @@ export class ChildProcess extends ITC {
117
92
  this.#setupServer()
118
93
  this.#setupInterceptors()
119
94
 
95
+ this.on('close', () => {
96
+ if (!globalThis.platformatic.events.emit('close')) {
97
+ // No user event, just exit without errors
98
+ process.exit(0)
99
+ }
100
+ })
101
+
120
102
  this.registerGlobals({
121
103
  logger: this.#logger,
122
104
  setOpenapiSchema: this.setOpenapiSchema.bind(this),
123
105
  setGraphqlSchema: this.setGraphqlSchema.bind(this),
124
106
  setConnectionString: this.setConnectionString.bind(this),
125
107
  setBasePath: this.setBasePath.bind(this),
126
- prometheus: { client, registry: this.#metricsRegistry },
127
- notifyConfig: this.#notifyConfig.bind(this)
108
+ prometheus: { client, registry: this.#metricsRegistry }
128
109
  })
129
110
  }
130
111
 
131
- registerGlobals (globals) {
132
- globalThis.platformatic = Object.assign(globalThis.platformatic ?? {}, globals)
133
- }
134
-
135
- setOpenapiSchema (schema) {
136
- this.notify('openapiSchema', schema)
137
- }
138
-
139
- setGraphqlSchema (schema) {
140
- this.notify('graphqlSchema', schema)
141
- }
142
-
143
- setConnectionString (connectionString) {
144
- this.notify('connectionString', connectionString)
145
- }
146
-
147
- setBasePath (basePath) {
148
- this.notify('basePath', basePath)
149
- }
150
-
151
112
  _setupListener (listener) {
152
113
  this.#listener = listener
153
114
 
@@ -268,7 +229,7 @@ export class ChildProcess extends ITC {
268
229
  #setupServer () {
269
230
  const subscribers = {
270
231
  asyncStart ({ options }) {
271
- // Unix socket, do nothing
232
+ // Unix socket, do nothing
272
233
  if (options.path) {
273
234
  return
274
235
  }
@@ -326,9 +287,9 @@ export class ChildProcess extends ITC {
326
287
 
327
288
  #setupHandlers () {
328
289
  const errorLabel =
329
- typeof globalThis.platformatic.workerId !== 'undefined'
330
- ? `worker ${globalThis.platformatic.workerId} of the service "${globalThis.platformatic.serviceId}"`
331
- : `service "${globalThis.platformatic.serviceId}"`
290
+ typeof globalThis.platformatic.workerId !== 'undefined'
291
+ ? `worker ${globalThis.platformatic.workerId} of the service "${globalThis.platformatic.serviceId}"`
292
+ : `service "${globalThis.platformatic.serviceId}"`
332
293
 
333
294
  function handleUnhandled (type, err) {
334
295
  this.#logger.error({ err: ensureLoggableError(err) }, `Child process for the ${errorLabel} threw an ${type}.`)
@@ -341,8 +302,24 @@ export class ChildProcess extends ITC {
341
302
  process.on('unhandledRejection', handleUnhandled.bind(this, 'unhandled rejection'))
342
303
  }
343
304
 
344
- #notifyConfig (config) {
345
- this.notify('config', config)
305
+ registerGlobals (globals) {
306
+ globalThis.platformatic = Object.assign(globalThis.platformatic ?? {}, globals)
307
+ }
308
+
309
+ setOpenapiSchema (schema) {
310
+ this.notify('openapiSchema', schema)
311
+ }
312
+
313
+ setGraphqlSchema (schema) {
314
+ this.notify('graphqlSchema', schema)
315
+ }
316
+
317
+ setConnectionString (connectionString) {
318
+ this.notify('connectionString', connectionString)
319
+ }
320
+
321
+ setBasePath (basePath) {
322
+ this.notify('basePath', basePath)
346
323
  }
347
324
  }
348
325
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/basic",
3
- "version": "2.67.0-alpha.1",
3
+ "version": "2.67.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -24,11 +24,11 @@
24
24
  "split2": "^4.2.0",
25
25
  "undici": "^7.0.0",
26
26
  "ws": "^8.18.0",
27
- "@platformatic/config": "2.67.0-alpha.1",
28
- "@platformatic/itc": "2.67.0-alpha.1",
29
- "@platformatic/telemetry": "2.67.0-alpha.1",
30
- "@platformatic/metrics": "2.67.0-alpha.1",
31
- "@platformatic/utils": "2.67.0-alpha.1"
27
+ "@platformatic/config": "2.67.0",
28
+ "@platformatic/itc": "2.67.0",
29
+ "@platformatic/telemetry": "2.67.0",
30
+ "@platformatic/metrics": "2.67.0",
31
+ "@platformatic/utils": "2.67.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "borp": "^0.20.0",
@@ -37,7 +37,6 @@
37
37
  "fastify": "^5.0.0",
38
38
  "get-port": "^7.1.0",
39
39
  "json-schema-to-typescript": "^15.0.0",
40
- "minimatch": "^10.0.1",
41
40
  "neostandard": "^0.12.0",
42
41
  "next": "^15.0.0",
43
42
  "react": "^18.3.1",
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/basic/2.67.0-alpha.1.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/basic/2.67.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Stackable",
5
5
  "type": "object",