wattpm 3.14.0 → 3.16.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
@@ -3,6 +3,7 @@ import { loadApplicationsCommands } from '@platformatic/runtime'
3
3
  import * as colorette from 'colorette'
4
4
  import { bold } from 'colorette'
5
5
  import { adminCommand } from './lib/commands/admin.js'
6
+ import { applicationsAddCommand, applicationsRemoveCommand } from './lib/commands/applications.js'
6
7
  import { buildCommand } from './lib/commands/build.js'
7
8
  import { createCommand } from './lib/commands/create.js'
8
9
  import { devCommand, reloadCommand, restartCommand, startCommand, stopCommand } from './lib/commands/execution.js'
@@ -105,6 +106,12 @@ export async function main () {
105
106
  case 'pprof':
106
107
  command = pprofCommand
107
108
  break
109
+ case 'applications:add':
110
+ command = applicationsAddCommand
111
+ break
112
+ case 'applications:remove':
113
+ command = applicationsRemoveCommand
114
+ break
108
115
  case 'admin':
109
116
  command = adminCommand
110
117
  break
@@ -0,0 +1,194 @@
1
+ import { getMatchingRuntime, RuntimeApiClient } from '@platformatic/control'
2
+ import { ensureLoggableError, logFatalError, parseArgs } from '@platformatic/foundation'
3
+ import { bold } from 'colorette'
4
+ import { readFile, stat, writeFile } from 'node:fs/promises'
5
+ import { basename, isAbsolute, relative, resolve } from 'node:path'
6
+
7
+ async function updateConfigFile (path, update) {
8
+ const contents = JSON.parse(await readFile(path, 'utf-8'))
9
+ await update(contents)
10
+ await writeFile(path, JSON.stringify(contents, null, 2), 'utf-8')
11
+ }
12
+
13
+ export async function applicationsAddCommand (logger, args) {
14
+ const {
15
+ values: { save },
16
+ positionals: allPositionals
17
+ } = parseArgs(
18
+ args,
19
+ {
20
+ save: {
21
+ type: 'boolean',
22
+ short: 's'
23
+ }
24
+ },
25
+ false
26
+ )
27
+
28
+ const client = new RuntimeApiClient()
29
+ try {
30
+ const [runtime, applications] = await getMatchingRuntime(client, allPositionals)
31
+ const config = await client.getRuntimeConfig(runtime.pid, true)
32
+ const root = config.__metadata.root
33
+
34
+ let toAdd = []
35
+ let added = 0
36
+
37
+ for (let app of applications) {
38
+ let spec
39
+
40
+ // Determine if app is a path to a directory or file, and load accordingly
41
+ try {
42
+ if (!isAbsolute(app)) {
43
+ app = resolve(root, app)
44
+ }
45
+
46
+ const pathStat = await stat(app)
47
+ if (pathStat.isDirectory()) {
48
+ spec = {
49
+ id: basename(app),
50
+ path: relative(root, app)
51
+ }
52
+ } else {
53
+ spec = JSON.parse(await readFile(app, 'utf-8'))
54
+ }
55
+ } catch (err) {
56
+ logFatalError(logger, `The path "${bold(app)}" does not exist or is not valid JSON.`)
57
+ return
58
+ }
59
+
60
+ const response = await client.addApplications(runtime.pid, spec, true)
61
+ added += response.length
62
+ toAdd = toAdd.concat(spec)
63
+ }
64
+
65
+ if (save) {
66
+ await updateConfigFile(config.__metadata.path, async config => {
67
+ config.applications = (config.applications ?? []).concat(toAdd)
68
+ })
69
+ }
70
+
71
+ logger.done(`Successfully added ${added} application${added > 1 ? 's' : ''} to the application.`)
72
+ } catch (error) {
73
+ if (error.code === 'PLT_CTR_RUNTIME_NOT_FOUND') {
74
+ return logFatalError(logger, 'Cannot find a matching runtime.')
75
+ /* c8 ignore next 7 - Hard to test */
76
+ } else {
77
+ return logFatalError(
78
+ logger,
79
+ { error: ensureLoggableError(error) },
80
+ `Cannot add applications to the application: ${error.message}`
81
+ )
82
+ }
83
+ } finally {
84
+ await client.close()
85
+ }
86
+ }
87
+
88
+ export async function applicationsRemoveCommand (logger, args) {
89
+ const {
90
+ values: { save },
91
+ positionals
92
+ } = parseArgs(
93
+ args,
94
+ {
95
+ save: {
96
+ type: 'boolean',
97
+ short: 's'
98
+ }
99
+ },
100
+ false
101
+ )
102
+
103
+ const client = new RuntimeApiClient()
104
+ try {
105
+ const [runtime, applications] = await getMatchingRuntime(client, positionals)
106
+
107
+ const removed = await client.removeApplications(runtime.pid, applications)
108
+
109
+ if (save) {
110
+ const config = await client.getRuntimeConfig(runtime.pid, true)
111
+ const absoluteAutoloadPath = resolve(config.__metadata.path, config.autoload.path)
112
+
113
+ await updateConfigFile(config.__metadata.path, async config => {
114
+ // Remove applications from all relevant sections
115
+ for (const app of removed) {
116
+ for (const section of ['applications', 'services', 'web']) {
117
+ if (Array.isArray(config[section])) {
118
+ config[section] = config[section].filter(a => a.id !== app.id)
119
+ }
120
+ }
121
+
122
+ if (config.autoload) {
123
+ if (app.path.startsWith(absoluteAutoloadPath)) {
124
+ config.autoload.exclude ??= []
125
+ config.autoload.exclude.push(relative(absoluteAutoloadPath, app.path))
126
+ }
127
+ }
128
+ }
129
+ })
130
+ }
131
+
132
+ logger.done(
133
+ `Successfully removed ${applications.length} application${applications.length > 1 ? 's' : ''} from the application.`
134
+ )
135
+ } catch (error) {
136
+ if (error.code === 'PLT_CTR_RUNTIME_NOT_FOUND') {
137
+ return logFatalError(logger, 'Cannot find a matching runtime.')
138
+ /* c8 ignore next 7 - Hard to test */
139
+ } else {
140
+ return logFatalError(
141
+ logger,
142
+ { error: ensureLoggableError(error) },
143
+ `Cannot remove applications from the application: ${error.message}`
144
+ )
145
+ }
146
+ } finally {
147
+ await client.close()
148
+ }
149
+ }
150
+
151
+ export const help = {
152
+ 'applications:add': {
153
+ usage: 'applications:add [id] <path>',
154
+ description: 'Add new applications to a running application',
155
+ args: [
156
+ {
157
+ name: 'id',
158
+ description:
159
+ 'The process ID or the name of the application (it can be omitted only if there is a single application running)'
160
+ },
161
+ {
162
+ name: 'path',
163
+ description: 'A folder containing an application or a JSON file containing the applications to add'
164
+ }
165
+ ],
166
+ options: [
167
+ {
168
+ usage: '-s, --save',
169
+ description: 'Save the added applications to the application configuration file'
170
+ }
171
+ ]
172
+ },
173
+ 'applications:remove': {
174
+ usage: 'applications:remove [id] [applications...]',
175
+ description: 'Remove applications from a running application',
176
+ args: [
177
+ {
178
+ name: 'id',
179
+ description:
180
+ 'The process ID or the name of the application (it can be omitted only if there is a single application running)'
181
+ },
182
+ {
183
+ name: 'applications',
184
+ description: 'The list of applications to remove'
185
+ }
186
+ ],
187
+ options: [
188
+ {
189
+ usage: '-s, --save',
190
+ description: 'Remove the removed applications from the application configuration file'
191
+ }
192
+ ]
193
+ }
194
+ }
@@ -19,6 +19,7 @@ export async function runDelegatedCommand (logger, packageManager, args) {
19
19
 
20
20
  const options = { stdio: 'inherit' }
21
21
 
22
+ /* c8 ignore next 4 - Covered by CI */
22
23
  if (platform() === 'win32') {
23
24
  options.shell = true
24
25
  options.windowsVerbatimArguments = true
@@ -10,7 +10,7 @@ import {
10
10
  import { create } from '@platformatic/runtime'
11
11
  import { bold } from 'colorette'
12
12
  import { spawn } from 'node:child_process'
13
- import { on } from 'node:events'
13
+ import { createInterface } from 'node:readline'
14
14
 
15
15
  export async function devCommand (logger, args) {
16
16
  const {
@@ -38,16 +38,32 @@ export async function devCommand (logger, args) {
38
38
 
39
39
  let runtime = await create(root, configurationFile, { start: true })
40
40
 
41
- // Add a watcher on the configurationFile so that we can eventually restart the runtime
42
- const watcher = new FileWatcher({ path: configurationFile })
43
- watcher.startWatching()
41
+ // Handle reloading via either file changes or stdin "rs" command
42
+ const { promise, reject } = Promise.withResolvers()
44
43
 
45
- // eslint-disable-next-line no-unused-vars
46
- for await (const _ of on(watcher, 'update')) {
47
- runtime.logger.info('The configuration file has changed, reloading the application ...')
44
+ async function reloadApplication () {
48
45
  await runtime.close()
49
- runtime = await create(root, configurationFile, { start: true })
46
+ runtime = await create(root, configurationFile, { start: true, reloaded: true })
50
47
  }
48
+
49
+ const watcher = new FileWatcher({ path: configurationFile })
50
+ watcher.startWatching()
51
+ watcher.on('update', () => {
52
+ runtime.logger.info('The configuration file has changed, reloading the application ...')
53
+ reloadApplication().catch(reject)
54
+ })
55
+
56
+ const rl = createInterface({ input: process.stdin })
57
+ rl.on('line', line => {
58
+ if (line.trim() !== 'rs') {
59
+ return
60
+ }
61
+
62
+ runtime.logger.info('Received "rs" from the stdin, Reloading the application ...')
63
+ reloadApplication().catch(reject)
64
+ })
65
+
66
+ return promise
51
67
  }
52
68
 
53
69
  export async function startCommand (logger, args) {
@@ -83,12 +99,11 @@ export async function startCommand (logger, args) {
83
99
  export async function stopCommand (logger, args) {
84
100
  const { positionals } = parseArgs(args, {}, false)
85
101
 
102
+ const client = new RuntimeApiClient()
86
103
  try {
87
- const client = new RuntimeApiClient()
88
104
  const [runtime] = await getMatchingRuntime(client, positionals)
89
105
 
90
106
  await client.stopRuntime(runtime.pid)
91
- await client.close()
92
107
 
93
108
  logger.done(`Runtime ${bold(runtime.packageName)} have been stopped.`)
94
109
  } catch (error) {
@@ -98,18 +113,19 @@ export async function stopCommand (logger, args) {
98
113
  } else {
99
114
  return logFatalError(logger, { error: ensureLoggableError(error) }, `Cannot stop the runtime: ${error.message}`)
100
115
  }
116
+ } finally {
117
+ await client.close()
101
118
  }
102
119
  }
103
120
 
104
121
  export async function restartCommand (logger, args) {
105
122
  const { positionals } = parseArgs(args, {}, false)
106
123
 
124
+ const client = new RuntimeApiClient()
107
125
  try {
108
- const client = new RuntimeApiClient()
109
126
  const [runtime, applications] = await getMatchingRuntime(client, positionals)
110
127
 
111
128
  await client.restartRuntime(runtime.pid, ...applications)
112
- await client.close()
113
129
 
114
130
  logger.done(`Runtime ${bold(runtime.packageName)} has been restarted.`)
115
131
  } catch (error) {
@@ -123,14 +139,16 @@ export async function restartCommand (logger, args) {
123
139
  `Cannot restart the runtime: ${error.message}`
124
140
  )
125
141
  }
142
+ } finally {
143
+ await client.close()
126
144
  }
127
145
  }
128
146
 
129
147
  export async function reloadCommand (logger, args) {
130
148
  const { positionals } = parseArgs(args, {}, false)
131
149
 
150
+ const client = new RuntimeApiClient()
132
151
  try {
133
- const client = new RuntimeApiClient()
134
152
  const [runtime] = await getMatchingRuntime(client, positionals)
135
153
 
136
154
  // Stop the previous runtime
@@ -147,7 +165,6 @@ export async function reloadCommand (logger, args) {
147
165
  })
148
166
 
149
167
  child.unref()
150
- await client.close()
151
168
 
152
169
  logger.done(`Runtime ${bold(runtime.packageName)} have been reloaded and it is now running as PID ${child.pid}.`)
153
170
  } catch (error) {
@@ -157,6 +174,8 @@ export async function reloadCommand (logger, args) {
157
174
  } else {
158
175
  return logFatalError(logger, { error: ensureLoggableError(error) }, `Cannot reload the runtime: ${error.message}`)
159
176
  }
177
+ } finally {
178
+ await client.close()
160
179
  }
161
180
  }
162
181
 
@@ -221,8 +240,7 @@ export const help = {
221
240
  },
222
241
  {
223
242
  name: 'application',
224
- description:
225
- 'The name of the application to restart (if omitted, all applications are restarted)'
243
+ description: 'The name of the application to restart (if omitted, all applications are restarted)'
226
244
  }
227
245
  ]
228
246
  },
@@ -9,7 +9,18 @@ function sanitizeHelp (raw) {
9
9
  async function loadCommands () {
10
10
  const commands = {}
11
11
 
12
- for (const file of ['build', 'create', 'execution', 'management', 'admin', 'logs', 'inject', 'metrics', 'pprof']) {
12
+ for (const file of [
13
+ 'build',
14
+ 'create',
15
+ 'execution',
16
+ 'applications',
17
+ 'management',
18
+ 'admin',
19
+ 'logs',
20
+ 'inject',
21
+ 'metrics',
22
+ 'pprof'
23
+ ]) {
13
24
  const category = await import(`./${file}.js`)
14
25
  Object.assign(commands, category.help)
15
26
  }
@@ -70,8 +70,8 @@ export async function injectCommand (logger, args) {
70
70
  )
71
71
 
72
72
  const outputStream = output ? createWriteStream(resolve(process.cwd(), output)) : process.stdout
73
+ const client = new RuntimeApiClient()
73
74
  try {
74
- const client = new RuntimeApiClient()
75
75
  const [runtime, positionals] = await getMatchingRuntime(client, allPositionals)
76
76
  let application = positionals[0]
77
77
 
@@ -137,8 +137,6 @@ export async function injectCommand (logger, args) {
137
137
  outputStream.end()
138
138
  await finished(outputStream)
139
139
  }
140
-
141
- await client.close()
142
140
  } catch (error) {
143
141
  if (error.code === 'PLT_CTR_RUNTIME_NOT_FOUND') {
144
142
  return logFatalError(logger, 'Cannot find a matching runtime.')
@@ -148,6 +146,8 @@ export async function injectCommand (logger, args) {
148
146
  } else {
149
147
  return logFatalError(logger, { error: ensureLoggableError(error) }, `Cannot perform a request: ${error.message}`)
150
148
  }
149
+ } finally {
150
+ await client.close()
151
151
  }
152
152
  }
153
153
 
@@ -18,8 +18,8 @@ export async function logsCommand (logger, args) {
18
18
  const output = values.pretty ? pinoPretty({ colorize: true }) : process.stdout
19
19
 
20
20
  let application
21
+ const client = new RuntimeApiClient()
21
22
  try {
22
- const client = new RuntimeApiClient()
23
23
  const [runtime, positionals] = await getMatchingRuntime(client, allPositionals)
24
24
  application = positionals[0]
25
25
 
@@ -54,6 +54,8 @@ export async function logsCommand (logger, args) {
54
54
  `Cannot stream ${application ? 'application' : 'runtime'} logs: ${error.message}`
55
55
  )
56
56
  }
57
+ } finally {
58
+ await client.close()
57
59
  }
58
60
  }
59
61
 
@@ -61,10 +61,9 @@ function formatDuration (duration) {
61
61
  }
62
62
 
63
63
  export async function psCommand (logger) {
64
+ const client = new RuntimeApiClient()
64
65
  try {
65
- const client = new RuntimeApiClient()
66
66
  const runtimes = await client.getRuntimes()
67
- await client.close()
68
67
 
69
68
  if (runtimes.length === 0) {
70
69
  logger.warn('No runtimes found.')
@@ -86,17 +85,18 @@ export async function psCommand (logger) {
86
85
  /* c8 ignore next 3 - Hard to test */
87
86
  } catch (error) {
88
87
  return logFatalError(logger, { error: ensureLoggableError(error) }, `Cannot list runtime: ${error.message}`)
88
+ } finally {
89
+ await client.close()
89
90
  }
90
91
  }
91
92
 
92
93
  export async function applicationsCommand (logger, args) {
93
94
  const { positionals } = parseArgs(args, {}, false)
95
+ const client = new RuntimeApiClient()
94
96
 
95
97
  try {
96
- const client = new RuntimeApiClient()
97
98
  const [runtime] = await getMatchingRuntime(client, positionals)
98
99
  const { production, applications } = await client.getRuntimeApplications(runtime.pid)
99
- await client.close()
100
100
 
101
101
  const headers = production
102
102
  ? [bold('Name'), bold('Workers'), bold('Type'), bold('Entrypoint')]
@@ -121,6 +121,8 @@ export async function applicationsCommand (logger, args) {
121
121
  `Cannot list runtime applications: ${error.message}`
122
122
  )
123
123
  }
124
+ } finally {
125
+ await client.close()
124
126
  }
125
127
  }
126
128
 
@@ -128,15 +130,14 @@ export async function envCommand (logger, args) {
128
130
  const { values, positionals: allPositionals } = parseArgs(args, { table: { type: 'boolean', short: 't' } }, false)
129
131
 
130
132
  let application
133
+ const client = new RuntimeApiClient()
131
134
  try {
132
- const client = new RuntimeApiClient()
133
135
  const [runtime, positionals] = await getMatchingRuntime(client, allPositionals)
134
136
  application = positionals[0]
135
137
 
136
138
  const env = application
137
139
  ? await client.getRuntimeApplicationEnv(runtime.pid, application)
138
140
  : await client.getRuntimeEnv(runtime.pid)
139
- await client.close()
140
141
 
141
142
  if (values.table) {
142
143
  console.log(
@@ -166,6 +167,8 @@ export async function envCommand (logger, args) {
166
167
  `Cannot get ${application ? 'application' : 'runtime'} environment variables: ${error.message}`
167
168
  )
168
169
  }
170
+ } finally {
171
+ await client.close()
169
172
  }
170
173
  }
171
174
 
@@ -173,15 +176,14 @@ export async function configCommand (logger, args) {
173
176
  const { positionals: allPositionals } = parseArgs(args, {}, false)
174
177
 
175
178
  let application
179
+ const client = new RuntimeApiClient()
176
180
  try {
177
- const client = new RuntimeApiClient()
178
181
  const [runtime, positionals] = await getMatchingRuntime(client, allPositionals)
179
182
  application = positionals[0]
180
183
 
181
184
  const config = application
182
185
  ? await client.getRuntimeApplicationConfig(runtime.pid, application)
183
186
  : await client.getRuntimeConfig(runtime.pid)
184
- await client.close()
185
187
 
186
188
  console.log(JSON.stringify(config, null, 2))
187
189
  } catch (error) {
@@ -197,6 +199,8 @@ export async function configCommand (logger, args) {
197
199
  `Cannot get ${application ? 'application' : 'runtime'} configuration: ${error.message}`
198
200
  )
199
201
  }
202
+ } finally {
203
+ await client.close()
200
204
  }
201
205
  }
202
206
 
@@ -3,15 +3,14 @@ import { ensureLoggableError, logFatalError, parseArgs } from '@platformatic/fou
3
3
  import { bold } from 'colorette'
4
4
 
5
5
  export async function metricsCommand (logger, args) {
6
+ const client = new RuntimeApiClient()
6
7
  try {
7
8
  const { values, positionals } = parseArgs(args, { format: { type: 'string', short: 'f', default: 'json' } }, false)
8
9
 
9
- const client = new RuntimeApiClient()
10
10
  const [runtime] = await getMatchingRuntime(client, positionals)
11
11
  const metrics = await client.getRuntimeMetrics(runtime.pid, { format: values.format })
12
12
  const result = values.format === 'text' ? metrics : JSON.stringify(metrics, null, 2)
13
13
  console.log(result)
14
- await client.close()
15
14
 
16
15
  logger.done(`Runtime ${bold(runtime.packageName)} have been stopped.`)
17
16
  } catch (error) {
@@ -21,6 +20,8 @@ export async function metricsCommand (logger, args) {
21
20
  } else {
22
21
  return logFatalError(logger, { error: ensureLoggableError(error) }, `Cannot reload the runtime: ${error.message}`)
23
22
  }
23
+ } finally {
24
+ client.close()
24
25
  }
25
26
  }
26
27
 
@@ -4,10 +4,16 @@ import { bold } from 'colorette'
4
4
  import { writeFile } from 'node:fs/promises'
5
5
 
6
6
  export async function pprofStartCommand (logger, args) {
7
+ const client = new RuntimeApiClient()
8
+
7
9
  try {
8
- const { positionals, values } = parseArgs(args, {
9
- type: { type: 'string', short: 't', default: 'cpu' }
10
- }, false)
10
+ const { positionals, values } = parseArgs(
11
+ args,
12
+ {
13
+ type: { type: 'string', short: 't', default: 'cpu' }
14
+ },
15
+ false
16
+ )
11
17
 
12
18
  // Validate profile type
13
19
  const type = values.type
@@ -15,7 +21,6 @@ export async function pprofStartCommand (logger, args) {
15
21
  return logFatalError(logger, `Invalid profile type: ${type}. Must be 'cpu' or 'heap'.`)
16
22
  }
17
23
 
18
- const client = new RuntimeApiClient()
19
24
  const [runtime, remainingPositionals] = await getMatchingRuntime(client, positionals)
20
25
  const { applications: runtimeApplications } = await client.getRuntimeApplications(runtime.pid)
21
26
 
@@ -28,7 +33,6 @@ export async function pprofStartCommand (logger, args) {
28
33
  // Start profiling for specific application
29
34
  const application = runtimeApplications.find(s => s.id === applicationId)
30
35
  if (!application) {
31
- await client.close()
32
36
  return logFatalError(logger, `Application not found: ${applicationId}`)
33
37
  }
34
38
 
@@ -45,8 +49,6 @@ export async function pprofStartCommand (logger, args) {
45
49
  }
46
50
  }
47
51
  }
48
-
49
- await client.close()
50
52
  } catch (error) {
51
53
  if (error.code === 'PLT_CTR_RUNTIME_NOT_FOUND') {
52
54
  return logFatalError(logger, 'Cannot find a matching runtime.')
@@ -55,14 +57,22 @@ export async function pprofStartCommand (logger, args) {
55
57
  } else {
56
58
  return logFatalError(logger, { error: ensureLoggableError(error) }, `Cannot start profiling: ${error.message}`)
57
59
  }
60
+ } finally {
61
+ await client.close()
58
62
  }
59
63
  }
60
64
 
61
65
  export async function pprofStopCommand (logger, args) {
66
+ const client = new RuntimeApiClient()
67
+
62
68
  try {
63
- const { positionals, values } = parseArgs(args, {
64
- type: { type: 'string', short: 't', default: 'cpu' }
65
- }, false)
69
+ const { positionals, values } = parseArgs(
70
+ args,
71
+ {
72
+ type: { type: 'string', short: 't', default: 'cpu' }
73
+ },
74
+ false
75
+ )
66
76
 
67
77
  // Validate profile type
68
78
  const type = values.type
@@ -70,7 +80,6 @@ export async function pprofStopCommand (logger, args) {
70
80
  return logFatalError(logger, `Invalid profile type: ${type}. Must be 'cpu' or 'heap'.`)
71
81
  }
72
82
 
73
- const client = new RuntimeApiClient()
74
83
  const [runtime, remainingPositionals] = await getMatchingRuntime(client, positionals)
75
84
  const { applications: runtimeApplications } = await client.getRuntimeApplications(runtime.pid)
76
85
 
@@ -84,14 +93,15 @@ export async function pprofStopCommand (logger, args) {
84
93
  // Stop profiling for specific application
85
94
  const application = runtimeApplications.find(s => s.id === applicationId)
86
95
  if (!application) {
87
- await client.close()
88
96
  return logFatalError(logger, `Application not found: ${applicationId}`)
89
97
  }
90
98
 
91
99
  const profileData = await client.stopApplicationProfiling(runtime.pid, applicationId, options)
92
100
  const filename = `pprof-${type}-${applicationId}-${timestamp}.pb`
93
101
  await writeFile(filename, Buffer.from(profileData))
94
- logger.info(`${type.toUpperCase()} profiling stopped for application ${bold(applicationId)}, profile saved to ${bold(filename)}`)
102
+ logger.info(
103
+ `${type.toUpperCase()} profiling stopped for application ${bold(applicationId)}, profile saved to ${bold(filename)}`
104
+ )
95
105
  } else {
96
106
  // Stop profiling for all applications
97
107
  for (const application of runtimeApplications) {
@@ -99,14 +109,14 @@ export async function pprofStopCommand (logger, args) {
99
109
  const profileData = await client.stopApplicationProfiling(runtime.pid, application.id, options)
100
110
  const filename = `pprof-${type}-${application.id}-${timestamp}.pb`
101
111
  await writeFile(filename, Buffer.from(profileData))
102
- logger.info(`${type.toUpperCase()} profiling stopped for application ${bold(application.id)}, profile saved to ${bold(filename)}`)
112
+ logger.info(
113
+ `${type.toUpperCase()} profiling stopped for application ${bold(application.id)}, profile saved to ${bold(filename)}`
114
+ )
103
115
  } catch (error) {
104
116
  logger.warn(`Failed to stop profiling for application ${application.id}: ${error.message}`)
105
117
  }
106
118
  }
107
119
  }
108
-
109
- await client.close()
110
120
  } catch (error) {
111
121
  if (error.code === 'PLT_CTR_RUNTIME_NOT_FOUND') {
112
122
  return logFatalError(logger, 'Cannot find a matching runtime.')
@@ -115,6 +125,8 @@ export async function pprofStopCommand (logger, args) {
115
125
  } else {
116
126
  return logFatalError(logger, { error: ensureLoggableError(error) }, `Cannot stop profiling: ${error.message}`)
117
127
  }
128
+ } finally {
129
+ await client.close()
118
130
  }
119
131
  }
120
132
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wattpm",
3
- "version": "3.14.0",
3
+ "version": "3.16.0",
4
4
  "description": "The Node.js Application Server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -29,9 +29,9 @@
29
29
  "pino-pretty": "^13.0.0",
30
30
  "split2": "^4.2.0",
31
31
  "table": "^6.8.2",
32
- "@platformatic/foundation": "3.14.0",
33
- "@platformatic/control": "3.14.0",
34
- "@platformatic/runtime": "3.14.0"
32
+ "@platformatic/control": "3.16.0",
33
+ "@platformatic/runtime": "3.16.0",
34
+ "@platformatic/foundation": "3.16.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "cleaner-spec-reporter": "^0.5.0",
@@ -42,7 +42,7 @@
42
42
  "neostandard": "^0.12.0",
43
43
  "typescript": "^5.5.4",
44
44
  "undici": "^7.0.0",
45
- "@platformatic/node": "3.14.0"
45
+ "@platformatic/node": "3.16.0"
46
46
  },
47
47
  "engines": {
48
48
  "node": ">=22.19.0"
package/schema.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://schemas.platformatic.dev/wattpm/3.14.0.json",
2
+ "$id": "https://schemas.platformatic.dev/wattpm/3.16.0.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema#",
4
4
  "title": "Platformatic Runtime Config",
5
5
  "type": "object",
@@ -64,6 +64,10 @@
64
64
  "useHttp": {
65
65
  "type": "boolean"
66
66
  },
67
+ "reuseTcpPorts": {
68
+ "type": "boolean",
69
+ "default": true
70
+ },
67
71
  "workers": {
68
72
  "anyOf": [
69
73
  {
@@ -342,6 +346,10 @@
342
346
  "useHttp": {
343
347
  "type": "boolean"
344
348
  },
349
+ "reuseTcpPorts": {
350
+ "type": "boolean",
351
+ "default": true
352
+ },
345
353
  "workers": {
346
354
  "anyOf": [
347
355
  {
@@ -618,6 +626,10 @@
618
626
  "useHttp": {
619
627
  "type": "boolean"
620
628
  },
629
+ "reuseTcpPorts": {
630
+ "type": "boolean",
631
+ "default": true
632
+ },
621
633
  "workers": {
622
634
  "anyOf": [
623
635
  {
@@ -894,6 +906,10 @@
894
906
  "useHttp": {
895
907
  "type": "boolean"
896
908
  },
909
+ "reuseTcpPorts": {
910
+ "type": "boolean",
911
+ "default": true
912
+ },
897
913
  "workers": {
898
914
  "anyOf": [
899
915
  {
@@ -1464,6 +1480,10 @@
1464
1480
  },
1465
1481
  "additionalProperties": false
1466
1482
  },
1483
+ "reuseTcpPorts": {
1484
+ "type": "boolean",
1485
+ "default": true
1486
+ },
1467
1487
  "startTimeout": {
1468
1488
  "default": 30000,
1469
1489
  "type": "number",