@platformatic/basic 3.41.0 → 3.43.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 CHANGED
@@ -434,6 +434,7 @@ export interface PlatformaticBasicConfig {
434
434
  env?: {
435
435
  [k: string]: string;
436
436
  };
437
+ envfile?: string;
437
438
  sourceMaps?: boolean;
438
439
  nodeModulesSourceMaps?: string[];
439
440
  scheduler?: {
package/lib/capability.js CHANGED
@@ -21,6 +21,7 @@ import { tracingChannel } from 'node:diagnostics_channel'
21
21
  import EventEmitter, { once } from 'node:events'
22
22
  import { existsSync } from 'node:fs'
23
23
  import { platform } from 'node:os'
24
+ import { isAbsolute, resolve } from 'node:path'
24
25
  import { pathToFileURL } from 'node:url'
25
26
  import { workerData } from 'node:worker_threads'
26
27
  import pino from 'pino'
@@ -101,7 +102,7 @@ export class BaseCapability extends EventEmitter {
101
102
  this.subprocessForceClose = false
102
103
  this.subprocessTerminationSignal = 'SIGINT'
103
104
  this.logger = this._initializeLogger()
104
- this.reuseTcpPorts = this.config.reuseTcpPorts ?? this.runtimeConfig.reuseTcpPorts
105
+ this.reuseTcpPorts = (this.config.reuseTcpPorts ?? this.runtimeConfig.reuseTcpPorts) && features.node.reusePort
105
106
  // True by default, can be overridden in subclasses. If false, it takes precedence over the runtime configuration
106
107
  this.exitOnUnhandledErrors = true
107
108
 
@@ -132,7 +133,9 @@ export class BaseCapability extends EventEmitter {
132
133
  setCustomHealthCheck: this.setCustomHealthCheck.bind(this),
133
134
  setCustomReadinessCheck: this.setCustomReadinessCheck.bind(this),
134
135
  notifyConfig: this.notifyConfig.bind(this),
135
- logger: this.logger
136
+ logger: this.logger,
137
+ isEntrypoint: this.isEntrypoint,
138
+ reuseTcpPorts: this.reuseTcpPorts
136
139
  })
137
140
 
138
141
  if (globalThis.platformatic.prometheus) {
@@ -541,7 +544,8 @@ export class BaseCapability extends EventEmitter {
541
544
  })
542
545
 
543
546
  const [url, clientWs] = await once(this.childManager, 'url')
544
- this.url = url
547
+
548
+ this.url = this._getEntrypointUrl(url)
545
549
  this.clientWs = clientWs
546
550
 
547
551
  await this._collectMetrics()
@@ -606,9 +610,8 @@ export class BaseCapability extends EventEmitter {
606
610
  runtimeBasePath: this.runtimeConfig?.basePath ?? null,
607
611
  wantsAbsoluteUrls: meta.gateway?.wantsAbsoluteUrls ?? false,
608
612
  exitOnUnhandledErrors: this.runtimeConfig.exitOnUnhandledErrors ?? true,
609
- /* c8 ignore next 2 - else */
610
- port: (this.isEntrypoint ? this.serverConfig?.port || 0 : undefined) ?? true,
611
613
  host: (this.isEntrypoint ? this.serverConfig?.hostname : undefined) ?? true,
614
+ port: this.serverConfig && typeof this.serverConfig.port === 'number' ? this.serverConfig.port : true,
612
615
  additionalServerOptions:
613
616
  typeof this.serverConfig?.backlog === 'number'
614
617
  ? {
@@ -655,7 +658,7 @@ export class BaseCapability extends EventEmitter {
655
658
  // This is not really important for the URL but sometimes it also a sign
656
659
  // that the process has been replaced and thus we need to update the client WebSocket
657
660
  childManager.on('url', (url, clientWs) => {
658
- this.url = url
661
+ this.url = this._getEntrypointUrl(url)
659
662
  this.clientWs = clientWs
660
663
  })
661
664
  }
@@ -668,6 +671,16 @@ export class BaseCapability extends EventEmitter {
668
671
  // This ensures subprocess uses the same Node.js version as the parent
669
672
  if (executable === 'node') {
670
673
  executable = process.execPath
674
+ // Search for the command in node_modules if needed
675
+ } else if (!isAbsolute(executable) && this.config.application?.preferLocalCommands) {
676
+ const applicationExecutable = resolve(this.root, 'node_modules', '.bin', executable)
677
+ const projectExecutable = resolve(process.cwd(), 'node_modules', '.bin', executable)
678
+
679
+ if (existsSync(applicationExecutable)) {
680
+ executable = applicationExecutable
681
+ } else if (existsSync(projectExecutable)) {
682
+ executable = projectExecutable
683
+ }
671
684
  }
672
685
 
673
686
  /* c8 ignore next 3 */
@@ -780,6 +793,23 @@ export class BaseCapability extends EventEmitter {
780
793
  return promise
781
794
  }
782
795
 
796
+ _getEntrypointUrl (raw) {
797
+ const port = this.config.application?.entrypointPort
798
+
799
+ if (typeof port !== 'number') {
800
+ return raw
801
+ }
802
+
803
+ const url = new URL(raw)
804
+
805
+ if (url.hostname === '[::]' || url.hostname === '0.0.0.0') {
806
+ url.hostname = 'localhost'
807
+ }
808
+
809
+ url.port = this.config.application.entrypointPort
810
+ return url.toString()
811
+ }
812
+
783
813
  async #collectMetrics () {
784
814
  const metricsConfig = {
785
815
  defaultMetrics: true,
package/lib/schema.js CHANGED
@@ -57,6 +57,17 @@ const buildableApplication = {
57
57
  },
58
58
  default: {},
59
59
  additionalProperties: false
60
+ },
61
+ entrypointPort: {
62
+ type: 'number'
63
+ },
64
+ changeDirectoryBeforeExecution: {
65
+ type: 'boolean',
66
+ default: false
67
+ },
68
+ preferLocalCommands: {
69
+ type: 'boolean',
70
+ default: true
60
71
  }
61
72
  },
62
73
  additionalProperties: false,
@@ -15,6 +15,7 @@ import { hostname, platform, tmpdir } from 'node:os'
15
15
  import { basename, join, resolve } from 'node:path'
16
16
  import { Duplex } from 'node:stream'
17
17
  import { fileURLToPath } from 'node:url'
18
+ import { isMainThread } from 'node:worker_threads'
18
19
  import pino from 'pino'
19
20
  import { Agent, Pool, setGlobalDispatcher } from 'undici'
20
21
  import { WebSocket } from 'ws'
@@ -369,7 +370,7 @@ export class ChildProcess extends ITC {
369
370
  // Create a duplex stream that sends output via notify
370
371
  const replStream = new Duplex({
371
372
  read () {},
372
- write: (chunk, encoding, callback) => {
373
+ write: (chunk, _encoding, callback) => {
373
374
  this.notify('repl:output', { data: chunk.toString() })
374
375
  callback()
375
376
  }
@@ -434,7 +435,7 @@ export class ChildProcess extends ITC {
434
435
  let promise = null
435
436
  const timeout = 1000
436
437
 
437
- const sendHealthSignal = async (signal) => {
438
+ const sendHealthSignal = async signal => {
438
439
  if (typeof signal !== 'object') {
439
440
  throw new Error('Health signal must be an object')
440
441
  }
@@ -513,13 +514,21 @@ export class ChildProcess extends ITC {
513
514
  return
514
515
  }
515
516
 
516
- const port = globalThis.platformatic.port
517
+ let port = globalThis.platformatic.port
517
518
  const host = globalThis.platformatic.host
519
+ const isEntrypoint = globalThis.platformatic.isEntrypoint
518
520
  const additionalOptions = globalThis.platformatic.additionalServerOptions ?? {}
519
521
 
520
- if (port !== false) {
521
- const hasFixedPort = typeof port === 'number'
522
- options.port = hasFixedPort ? port : 0
522
+ if (typeof port !== 'number' && port !== false) {
523
+ port = 0
524
+ }
525
+
526
+ // Check if we need to override the port only if a static port is being requested
527
+ if (port !== false && port !== 0) {
528
+ // The user application has requested a specific port, which is not the entrypoint one. Override it.
529
+ if (options.port !== port && isEntrypoint) {
530
+ options.port = port
531
+ }
523
532
  }
524
533
 
525
534
  if (typeof host === 'string') {
@@ -711,6 +720,10 @@ async function main () {
711
720
  await importFile(script)
712
721
  }
713
722
 
723
+ if (data.config.application?.changeDirectoryBeforeExecution && data.root && isMainThread) {
724
+ process.chdir(fileURLToPath(data.root))
725
+ }
726
+
714
727
  const childProcess = new ChildProcess(executable)
715
728
  globalThis[Symbol.for('plt.children.itc')] = childProcess
716
729
  globalThis.platformatic.itc = childProcess
@@ -10,15 +10,22 @@ export function createServerListener (overridePort = true, overrideHost = false,
10
10
  return
11
11
  }
12
12
 
13
- if (overridePort !== false) {
14
- const hasFixedPort = typeof overridePort === 'number'
15
- options.port = hasFixedPort ? overridePort : 0
13
+ if (typeof overridePort !== 'number' && overridePort !== false) {
14
+ overridePort = 0
16
15
  }
17
16
 
18
17
  if (typeof overrideHost === 'string') {
19
18
  options.host = overrideHost
20
19
  }
21
20
 
21
+ // Check if we need to override the port only if a static port is being requested
22
+ if (overridePort !== false && overridePort !== 0) {
23
+ // The user application has requested a specific port, which is not the entrypoint one. Override it.
24
+ if (options.port !== overridePort && globalThis.platformatic.isEntrypoint) {
25
+ options.port = overridePort
26
+ }
27
+ }
28
+
22
29
  Object.assign(options, additionalOptions)
23
30
  globalThis.platformatic?.events?.emitAndNotify('serverOptions', options)
24
31
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/basic",
3
- "version": "3.41.0",
3
+ "version": "3.43.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -25,10 +25,10 @@
25
25
  "split2": "^4.2.0",
26
26
  "undici": "^7.0.0",
27
27
  "ws": "^8.18.0",
28
- "@platformatic/foundation": "3.41.0",
29
- "@platformatic/itc": "3.41.0",
30
- "@platformatic/telemetry": "3.41.0",
31
- "@platformatic/metrics": "3.41.0"
28
+ "@platformatic/foundation": "3.43.0",
29
+ "@platformatic/itc": "3.43.0",
30
+ "@platformatic/telemetry": "3.43.0",
31
+ "@platformatic/metrics": "3.43.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "cleaner-spec-reporter": "^0.5.0",
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/@platformatic/basic/3.41.0.json",
2
+ "$id": "https://schemas.platformatic.dev/@platformatic/basic/3.43.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Basic Config",
5
5
  "type": "object",
@@ -1647,6 +1647,9 @@
1647
1647
  "type": "string"
1648
1648
  }
1649
1649
  },
1650
+ "envfile": {
1651
+ "type": "string"
1652
+ },
1650
1653
  "sourceMaps": {
1651
1654
  "type": "boolean",
1652
1655
  "default": false