@milaboratories/pl-deployments 1.8.0 → 1.8.2

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-deployments",
3
- "version": "1.8.0",
4
- "pl-version": "1.30.0",
3
+ "version": "1.8.2",
4
+ "pl-version": "1.31.3",
5
5
  "description": "MiLaboratories Platforma Backend code service run wrapper",
6
6
  "engines": {
7
7
  "node": ">=20.16.0"
@@ -53,8 +53,8 @@
53
53
  "undici": "~7.5.0",
54
54
  "yaml": "^2.7.0",
55
55
  "zod": "~3.23.8",
56
- "@milaboratories/pl-config": "^1.4.5",
57
- "@milaboratories/ts-helpers": "^1.1.5"
56
+ "@milaboratories/ts-helpers": "^1.1.5",
57
+ "@milaboratories/pl-config": "^1.4.6"
58
58
  },
59
59
  "scripts": {
60
60
  "type-check": "tsc --noEmit --composite false",
@@ -52,6 +52,8 @@ controllers:
52
52
 
53
53
  runner:
54
54
  type: local
55
+ resources:
56
+ cpu: 5
55
57
  storageRoot: './storages/work'
56
58
 
57
59
  packageLoader:
@@ -1,12 +1,15 @@
1
- import { test } from 'vitest';
2
- import type { LocalPlOptions } from './pl';
1
+ import { test, expect } from 'vitest';
3
2
  import { localPlatformaInit } from './pl';
4
3
  import { ConsoleLoggerAdapter, sleep } from '@milaboratories/ts-helpers';
5
4
  import * as fs from 'fs/promises';
6
5
  import upath from 'upath';
7
- import { processStop } from './process';
6
+ import { ProcessOptions, processStop } from './process';
8
7
  import * as yaml from 'yaml';
9
8
  import * as os from 'os';
9
+ import { mergeDefaultOps } from './pl';
10
+ import type { LocalPlOptions, LocalPlOptionsFull } from './pl';
11
+ import { plProcessOps } from './pl';
12
+ import { describe, it, beforeEach, afterEach } from 'vitest';
10
13
 
11
14
  test(
12
15
  'should start and stop platforma of the current version with hardcoded config',
@@ -37,7 +40,7 @@ test(
37
40
 
38
41
  test(
39
42
  'should close old platforma when starting a new one if the option is set',
40
- { timeout: 25000 },
43
+ { timeout: 35000 },
41
44
  async ({ expect }) => {
42
45
  const logger = new ConsoleLoggerAdapter();
43
46
 
@@ -127,3 +130,314 @@ async function prepareDirForTestConfig() {
127
130
 
128
131
  return dir;
129
132
  }
133
+
134
+ const mergeDefaultOpsCases: {
135
+ name: string;
136
+ input: {
137
+ ops: LocalPlOptions;
138
+ numCpu: number;
139
+ };
140
+ expected: LocalPlOptionsFull;
141
+ }[] = [
142
+ {
143
+ name: 'should set default values when minimal input is provided',
144
+ input: {
145
+ ops: {
146
+ workingDir: '/test',
147
+ config: 'config',
148
+ plBinary: { type: 'Download', version: '1.29.2' },
149
+ },
150
+ numCpu: 4,
151
+ },
152
+ expected: {
153
+ workingDir: '/test',
154
+ config: 'config',
155
+ plBinary: { type: 'Download', version: '1.29.2' },
156
+ spawnOptions: {
157
+ env: {
158
+ GOMAXPROCS: '4',
159
+ },
160
+ },
161
+ closeOld: true,
162
+ },
163
+ },
164
+ {
165
+ name: 'should override outermost options when provided',
166
+ input: {
167
+ ops: {
168
+ workingDir: '/test',
169
+ config: 'config',
170
+ // we provided plBinary and closeOld, they should appear in the result
171
+ plBinary: { type: 'Local', path: '/custom/binary' },
172
+ closeOld: false,
173
+ },
174
+ numCpu: 2,
175
+ },
176
+ expected: {
177
+ workingDir: '/test',
178
+ config: 'config',
179
+ spawnOptions: {
180
+ env: {
181
+ GOMAXPROCS: '2',
182
+ },
183
+ },
184
+ plBinary: { type: 'Local', path: '/custom/binary' },
185
+ closeOld: false,
186
+ },
187
+ },
188
+ {
189
+ name: 'should merge env variables when provided',
190
+ input: {
191
+ ops: {
192
+ workingDir: '/test',
193
+ config: 'config',
194
+ plBinary: { type: 'Download', version: '1.29.2' },
195
+ spawnOptions: {
196
+ env: {
197
+ NODE_ENV: 'test',
198
+ DEBUG: 'true',
199
+ },
200
+ },
201
+ },
202
+ numCpu: 3,
203
+ },
204
+ expected: {
205
+ workingDir: '/test',
206
+ config: 'config',
207
+ plBinary: { type: 'Download', version: '1.29.2' },
208
+ spawnOptions: {
209
+ env: {
210
+ GOMAXPROCS: '3',
211
+ NODE_ENV: 'test',
212
+ DEBUG: 'true',
213
+ },
214
+ },
215
+ closeOld: true,
216
+ },
217
+ },
218
+ {
219
+ name: 'should override other spawnOptions properties',
220
+ input: {
221
+ ops: {
222
+ workingDir: '/test',
223
+ config: 'config',
224
+ plBinary: { type: 'Download', version: '1.29.2' },
225
+ spawnOptions: {
226
+ stdio: 'inherit',
227
+ detached: true,
228
+ },
229
+ },
230
+ numCpu: 2,
231
+ },
232
+ expected: {
233
+ workingDir: '/test',
234
+ config: 'config',
235
+ plBinary: { type: 'Download', version: '1.29.2' },
236
+ spawnOptions: {
237
+ env: {
238
+ GOMAXPROCS: '2',
239
+ },
240
+ stdio: 'inherit',
241
+ detached: true,
242
+ },
243
+ closeOld: true,
244
+ },
245
+ },
246
+ {
247
+ name: 'should handle complex case with multiple overrides',
248
+ input: {
249
+ ops: {
250
+ workingDir: '/test',
251
+ config: 'config',
252
+ plBinary: { type: 'Local', path: '/custom/binary' },
253
+ closeOld: false,
254
+ spawnOptions: {
255
+ env: {
256
+ NODE_ENV: 'production',
257
+ LOG_LEVEL: 'debug',
258
+ },
259
+ cwd: '/custom/dir',
260
+ windowsHide: false,
261
+ },
262
+ },
263
+ numCpu: 6,
264
+ },
265
+ expected: {
266
+ workingDir: '/test',
267
+ config: 'config',
268
+ plBinary: { type: 'Local', path: '/custom/binary' },
269
+ closeOld: false,
270
+ spawnOptions: {
271
+ env: {
272
+ GOMAXPROCS: '6',
273
+ NODE_ENV: 'production',
274
+ LOG_LEVEL: 'debug',
275
+ },
276
+ cwd: '/custom/dir',
277
+ windowsHide: false,
278
+ },
279
+ },
280
+ },
281
+ ];
282
+
283
+ test.each(mergeDefaultOpsCases)('mergeDefaultOps: $name', ({ name, input, expected }) => {
284
+ const result = mergeDefaultOps(input.ops, input.numCpu);
285
+
286
+ expect(result).toEqual(expected);
287
+ });
288
+
289
+ const plProcessOpsCases: {
290
+ name: string;
291
+ input: {
292
+ binaryPath: string;
293
+ configPath: string;
294
+ ops: LocalPlOptionsFull;
295
+ workDir: string;
296
+ };
297
+ expected: ProcessOptions;
298
+ }[] = [
299
+ {
300
+ name: 'should set basic options with minimal input',
301
+ input: {
302
+ binaryPath: '/path/to/binary',
303
+ configPath: '/path/to/config.yaml',
304
+ ops: {
305
+ workingDir: '/work/dir',
306
+ config: 'config-content',
307
+ plBinary: { type: 'Download', version: '1.29.2' },
308
+ spawnOptions: {},
309
+ closeOld: true,
310
+ },
311
+ workDir: '/work/dir',
312
+ },
313
+ expected: {
314
+ cmd: '/path/to/binary',
315
+ args: ['--config', '/path/to/config.yaml'],
316
+ opts: {
317
+ env: {},
318
+ cwd: '/work/dir',
319
+ stdio: ['pipe', 'ignore', 'inherit'],
320
+ windowsHide: true,
321
+ },
322
+ },
323
+ },
324
+ {
325
+ name: 'should merge environment variables when provided',
326
+ input: {
327
+ binaryPath: '/path/to/binary',
328
+ configPath: '/config.yaml',
329
+ ops: {
330
+ workingDir: '/work',
331
+ config: 'content',
332
+ plBinary: { type: 'Download', version: '1.29.2' },
333
+ spawnOptions: {
334
+ env: {
335
+ DEBUG: 'true',
336
+ LOG_LEVEL: 'info',
337
+ },
338
+ },
339
+ closeOld: true,
340
+ },
341
+ workDir: '/work',
342
+ },
343
+ expected: {
344
+ cmd: '/path/to/binary',
345
+ args: ['--config', '/config.yaml'],
346
+ opts: {
347
+ env: {
348
+ DEBUG: 'true',
349
+ LOG_LEVEL: 'info',
350
+ },
351
+ cwd: '/work',
352
+ stdio: ['pipe', 'ignore', 'inherit'],
353
+ windowsHide: true,
354
+ },
355
+ },
356
+ },
357
+ {
358
+ name: 'should override spawn options when provided',
359
+ input: {
360
+ binaryPath: '/binary',
361
+ configPath: '/config.yaml',
362
+ ops: {
363
+ workingDir: '/work',
364
+ config: 'content',
365
+ plBinary: { type: 'Download', version: '1.29.2' },
366
+ spawnOptions: {
367
+ stdio: 'inherit',
368
+ detached: true,
369
+ shell: true,
370
+ },
371
+ closeOld: true,
372
+ },
373
+ workDir: '/work',
374
+ },
375
+ expected: {
376
+ cmd: '/binary',
377
+ args: ['--config', '/config.yaml'],
378
+ opts: {
379
+ env: {},
380
+ cwd: '/work',
381
+ stdio: 'inherit',
382
+ detached: true,
383
+ shell: true,
384
+ windowsHide: true,
385
+ },
386
+ },
387
+ },
388
+ {
389
+ name: 'should handle complex case with multiple options',
390
+ input: {
391
+ binaryPath: '/bin/platforma',
392
+ configPath: '/etc/platforma/config.yaml',
393
+ ops: {
394
+ workingDir: '/var/platforma',
395
+ config: 'yaml content',
396
+ plBinary: { type: 'Download', version: '1.29.2' },
397
+ spawnOptions: {
398
+ env: {
399
+ PL_DEBUG: 'true',
400
+ PL_MODE: 'development',
401
+ GOMAXPROCS: '4',
402
+ },
403
+ stdio: ['ignore', 'pipe', 'pipe'],
404
+ detached: false,
405
+ windowsHide: false,
406
+ uid: 1000,
407
+ gid: 1000,
408
+ },
409
+ closeOld: true,
410
+ },
411
+ workDir: '/var/platforma/runtime',
412
+ },
413
+ expected: {
414
+ cmd: '/bin/platforma',
415
+ args: ['--config', '/etc/platforma/config.yaml'],
416
+ opts: {
417
+ env: {
418
+ PL_DEBUG: 'true',
419
+ PL_MODE: 'development',
420
+ GOMAXPROCS: '4',
421
+ },
422
+ cwd: '/var/platforma/runtime',
423
+ stdio: ['ignore', 'pipe', 'pipe'],
424
+ detached: false,
425
+ windowsHide: false,
426
+ uid: 1000,
427
+ gid: 1000,
428
+ },
429
+ },
430
+ },
431
+ ];
432
+
433
+ test.each(plProcessOpsCases)('plProcessOps: $name', ({ name, input, expected }) => {
434
+ const result = plProcessOps(
435
+ input.binaryPath,
436
+ input.configPath,
437
+ input.ops,
438
+ input.workDir,
439
+ {},
440
+ );
441
+
442
+ expect(result).toEqual(expected);
443
+ });
package/src/local/pl.ts CHANGED
@@ -17,6 +17,7 @@ import { withTrace } from './trace';
17
17
  import upath from 'upath';
18
18
  import fsp from 'node:fs/promises';
19
19
  import type { Required } from 'utility-types';
20
+ import * as os from 'node:os';
20
21
 
21
22
  export const LocalConfigYaml = 'config-local.yaml';
22
23
 
@@ -134,19 +135,18 @@ export type LocalPlOptions = {
134
135
  readonly onCloseAndErrorNoStop?: (pl: LocalPl) => Promise<void>;
135
136
  };
136
137
 
137
- type LocalPlOptionsFull = Required<LocalPlOptions, 'plBinary' | 'spawnOptions' | 'closeOld'>;
138
+ export type LocalPlOptionsFull = Required<LocalPlOptions, 'plBinary' | 'spawnOptions' | 'closeOld'>;
138
139
 
139
140
  /**
140
141
  * Starts pl-core, if the option was provided downloads a binary, reads license environments etc.
141
142
  */
142
143
  export async function localPlatformaInit(logger: MiLogger, _ops: LocalPlOptions): Promise<LocalPl> {
143
144
  // filling-in default values
144
- const ops = {
145
- plBinary: newDefaultPlBinarySource(),
146
- spawnOptions: {},
147
- closeOld: true,
148
- ..._ops,
149
- } satisfies LocalPlOptionsFull;
145
+
146
+ // Backend could consume a lot of CPU power,
147
+ // we want to keep at least a couple for UI and other apps to work.
148
+ const numCpu = Math.max(os.cpus().length - 2, 1);
149
+ const ops = mergeDefaultOps(_ops, numCpu);
150
150
 
151
151
  return await withTrace(logger, async (trace, t) => {
152
152
  trace('startOptions', { ...ops, config: 'too wordy' });
@@ -164,20 +164,9 @@ export async function localPlatformaInit(logger: MiLogger, _ops: LocalPlOptions)
164
164
 
165
165
  const plBinPath = upath.join(workDir, 'binaries');
166
166
  const baseBinaryPath = await resolveLocalPlBinaryPath(logger, plBinPath, ops.plBinary);
167
-
168
167
  const binaryPath = trace('binaryPath', upath.join('binaries', baseBinaryPath));
169
168
 
170
- const processOpts: ProcessOptions = {
171
- cmd: binaryPath,
172
- args: ['--config', configPath],
173
- opts: {
174
- env: { ...process.env },
175
- cwd: workDir,
176
- stdio: ['pipe', 'ignore', 'inherit'],
177
- windowsHide: true, // hide a terminal on Windows
178
- ...ops.spawnOptions,
179
- },
180
- };
169
+ const processOpts = plProcessOps(binaryPath, configPath, ops, workDir, process.env);
181
170
  trace('processOpts', {
182
171
  cmd: processOpts.cmd,
183
172
  args: processOpts.args,
@@ -220,3 +209,67 @@ async function localPlatformaReadPidAndStop(
220
209
  return t;
221
210
  });
222
211
  }
212
+
213
+ /** Gets default options for the whole init process
214
+ * and overrides them with the provided options. */
215
+ export function mergeDefaultOps(ops: LocalPlOptions, numCpu: number): LocalPlOptionsFull {
216
+ const result: {
217
+ plBinary: PlBinarySource;
218
+ spawnOptions: SpawnOptions;
219
+ closeOld: boolean;
220
+ } = {
221
+ plBinary: newDefaultPlBinarySource(),
222
+ spawnOptions: {
223
+ env: {
224
+ GOMAXPROCS: String(numCpu),
225
+ },
226
+ },
227
+ closeOld: true,
228
+ };
229
+
230
+ if (ops.spawnOptions?.env) {
231
+ result.spawnOptions.env = { ...result.spawnOptions.env, ...ops.spawnOptions.env };
232
+ }
233
+
234
+ if (ops.spawnOptions) {
235
+ const withoutEnv = { ...ops.spawnOptions };
236
+ delete withoutEnv['env'];
237
+ result.spawnOptions = { ...result.spawnOptions, ...withoutEnv };
238
+ }
239
+
240
+ const withoutSpawnOps = { ...ops };
241
+ delete withoutSpawnOps['spawnOptions'];
242
+
243
+ return { ...result, ...withoutSpawnOps };
244
+ }
245
+
246
+ /** Gets default options for a platforma local binary
247
+ * and overrides them with the provided options. */
248
+ export function plProcessOps(
249
+ binaryPath: any,
250
+ configPath: string,
251
+ ops: LocalPlOptionsFull,
252
+ workDir: string,
253
+ defaultEnv: Record<string, string | undefined>,
254
+ ): ProcessOptions {
255
+ const result: ProcessOptions = {
256
+ cmd: binaryPath,
257
+ args: ['--config', configPath],
258
+ opts: {
259
+ env: { ...defaultEnv },
260
+ cwd: workDir,
261
+ stdio: ['pipe', 'ignore', 'inherit'],
262
+ windowsHide: true, // hide a terminal on Windows
263
+ },
264
+ };
265
+
266
+ if (ops.spawnOptions?.env) {
267
+ result.opts.env = { ...result.opts.env, ...ops.spawnOptions.env };
268
+ }
269
+
270
+ const withoutEnv = { ...ops.spawnOptions };
271
+ delete withoutEnv['env'];
272
+ result.opts = { ...result.opts, ...withoutEnv };
273
+
274
+ return result;
275
+ }