@fuzdev/fuz_util 0.48.0 → 0.48.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/dist/process.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type SpawnOptions, type ChildProcess } from 'node:child_process';
1
+ import { spawn as node_spawn_child_process, type SpawnOptions, type ChildProcess } from 'node:child_process';
2
2
  /**
3
3
  * Spawn failed before the process could run.
4
4
  *
@@ -64,6 +64,10 @@ export interface SpawnProcessOptions extends SpawnOptions {
64
64
  * Sends SIGTERM when exceeded. A value of 0 triggers immediate SIGTERM.
65
65
  */
66
66
  timeout_ms?: number;
67
+ /**
68
+ * Custom spawn function for testing. Defaults to `node:child_process` spawn.
69
+ */
70
+ spawn_child_process?: typeof node_spawn_child_process;
67
71
  }
68
72
  /**
69
73
  * Options for killing processes.
@@ -80,6 +84,16 @@ export interface DespawnOptions {
80
84
  */
81
85
  timeout_ms?: number;
82
86
  }
87
+ /**
88
+ * Result of spawning a detached process.
89
+ */
90
+ export type SpawnDetachedResult = {
91
+ ok: true;
92
+ child: ChildProcess;
93
+ } | {
94
+ ok: false;
95
+ message: string;
96
+ };
83
97
  /**
84
98
  * Handle for a spawned process with access to the child and completion promise.
85
99
  */
@@ -245,6 +259,33 @@ export declare const despawn_all: (options?: DespawnOptions) => Promise<Array<Sp
245
259
  * @see ProcessRegistry.attach_error_handler
246
260
  */
247
261
  export declare const attach_process_error_handler: (options?: Parameters<ProcessRegistry["attach_error_handler"]>[0]) => (() => void);
262
+ /**
263
+ * Spawns a detached process that continues after parent exits.
264
+ *
265
+ * Unlike other spawn functions, this is NOT tracked in any ProcessRegistry.
266
+ * The spawned process is meant to outlive the parent (e.g., daemon processes).
267
+ *
268
+ * @param command - The command to run
269
+ * @param args - Arguments to pass to the command
270
+ * @param options - Spawn options (use `stdio` to redirect output to file descriptors)
271
+ * @returns Result with pid on success, or error message on failure
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * // Simple detached process
276
+ * const result = spawn_detached('node', ['daemon.js'], {cwd: '/app'});
277
+ *
278
+ * // With log file (caller handles file opening)
279
+ * import {openSync, closeSync} from 'node:fs';
280
+ * const log_fd = openSync('/var/log/daemon.log', 'a');
281
+ * const result = spawn_detached('node', ['daemon.js'], {
282
+ * cwd: '/app',
283
+ * stdio: ['ignore', log_fd, log_fd],
284
+ * });
285
+ * closeSync(log_fd);
286
+ * ```
287
+ */
288
+ export declare const spawn_detached: (command: string, args?: ReadonlyArray<string>, options?: SpawnOptions) => SpawnDetachedResult;
248
289
  /**
249
290
  * Formats a child process for display.
250
291
  *
@@ -1 +1 @@
1
- {"version":3,"file":"process.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/process.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"process.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/process.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,IAAI,wBAAwB,EACjC,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,MAAM,oBAAoB,CAAC;AAa5B;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,IAAI,CAAC;CACb;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,YAAY,CAAC;IACpB,KAAK,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAMrF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,WAAW,KAAG,MAAM,IAAI,gBAChD,CAAC;AAEvB;;GAEG;AACH,eAAO,MAAM,wBAAwB,GAAI,QAAQ,WAAW,KAAG,MAAM,IAAI,mBAClD,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,sBAAsB,GAAI,QAAQ,WAAW,KAAG,MAAM,IAAI,iBAClD,CAAC;AAMtB;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACxD;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;OAEG;IACH,mBAAmB,CAAC,EAAE,OAAO,wBAAwB,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAAC,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAC,GAAG;IAAC,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAC;AAMjG;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,0CAA0C;IAC1C,KAAK,EAAE,YAAY,CAAC;IACpB,sCAAsC;IACtC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,qDAAqD;IACrD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qDAAqD;IACrD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AA2ED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,eAAe;;IAC3B,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,YAAY,CAAC,CAAa;IAIlD;;;;;;;;OAQG;IACH,KAAK,CACJ,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,aAAa,CAAC,MAAM,CAAM,EAChC,OAAO,CAAC,EAAE,mBAAmB,GAC3B,cAAc;IAgCjB;;;;;;;;;;;OAWG;IACG,SAAS,CACd,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,aAAa,CAAC,MAAM,CAAM,EAChC,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC;IA2BtB;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IAsClF;;;;;OAKG;IACG,WAAW,CAAC,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAIxE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,oBAAoB,CAAC,OAAO,CAAC,EAAE;QAC9B,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,uBAAuB,KAAK,MAAM,GAAG,IAAI,CAAC;QACvF,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,uBAAuB,KAAK,MAAM,GAAG,IAAI,CAAC;QACvF,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,uBAAuB,KAAK,IAAI,CAAC;QAC5E,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACpC,GAAG,MAAM,IAAI;CAmDd;AAMD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,iBAAwB,CAAC;AAM9D;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,GACzB,SAAS,MAAM,EACf,OAAM,aAAa,CAAC,MAAM,CAAM,EAChC,UAAU,mBAAmB,KAC3B,cAAwE,CAAC;AAE5E;;;;;;;;;GASG;AACH,eAAO,MAAM,KAAK,GACjB,SAAS,MAAM,EACf,OAAM,aAAa,CAAC,MAAM,CAAM,EAChC,UAAU,mBAAmB,KAC3B,OAAO,CAAC,WAAW,CAAiD,CAAC;AAExE;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GACrB,SAAS,MAAM,EACf,OAAM,aAAa,CAAC,MAAM,CAAM,EAChC,UAAU,mBAAmB,KAC3B,OAAO,CAAC,UAAU,CAA+D,CAAC;AAErF;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,YAAY,EAAE,UAAU,cAAc,KAAG,OAAO,CAAC,WAAW,CAC1C,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,UAAU,cAAc,KAAG,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CACnC,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,GACxC,UAAU,UAAU,CAAC,eAAe,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,CAAC,KAC9D,CAAC,MAAM,IAAI,CAA2D,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,cAAc,GAC1B,SAAS,MAAM,EACf,OAAM,aAAa,CAAC,MAAM,CAAM,EAChC,UAAU,YAAY,KACpB,mBAmBF,CAAC;AAMF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,YAAY,KAAG,MACkD,CAAC;AAE7G;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,WAAW,KAAG,MAKxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAAI,QAAQ,WAAW,KAAG,MAI7D,CAAC;AAMF;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,iDAAiD;IACjD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACtC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,yBAAyB,GACrC,SAAS,MAAM,EACf,OAAM,aAAa,CAAC,MAAM,CAAM,EAChC,UAAU,mBAAmB,KAC3B,kBAqFF,CAAC;AAMF;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,GAAI,KAAK,MAAM,KAAG,OAcpD,CAAC"}
package/dist/process.js CHANGED
@@ -1,4 +1,4 @@
1
- import { spawn as spawn_child_process, } from 'node:child_process';
1
+ import { spawn as node_spawn_child_process, } from 'node:child_process';
2
2
  import { styleText as st } from 'node:util';
3
3
  import { Logger } from './log.js';
4
4
  import { print_error, print_key_value } from './print.js';
@@ -117,7 +117,7 @@ export class ProcessRegistry {
117
117
  * @returns Handle with `child` process and `closed` promise
118
118
  */
119
119
  spawn(command, args = [], options) {
120
- const { signal, timeout_ms, ...spawn_options } = options ?? {};
120
+ const { signal, timeout_ms, spawn_child_process = node_spawn_child_process, ...spawn_options } = options ?? {};
121
121
  validate_timeout_ms(timeout_ms);
122
122
  const child = spawn_child_process(command, args, { stdio: 'inherit', ...spawn_options });
123
123
  this.processes.add(child);
@@ -351,6 +351,50 @@ export const despawn_all = (options) => process_registry_default.despawn_all(opt
351
351
  * @see ProcessRegistry.attach_error_handler
352
352
  */
353
353
  export const attach_process_error_handler = (options) => process_registry_default.attach_error_handler(options);
354
+ /**
355
+ * Spawns a detached process that continues after parent exits.
356
+ *
357
+ * Unlike other spawn functions, this is NOT tracked in any ProcessRegistry.
358
+ * The spawned process is meant to outlive the parent (e.g., daemon processes).
359
+ *
360
+ * @param command - The command to run
361
+ * @param args - Arguments to pass to the command
362
+ * @param options - Spawn options (use `stdio` to redirect output to file descriptors)
363
+ * @returns Result with pid on success, or error message on failure
364
+ *
365
+ * @example
366
+ * ```ts
367
+ * // Simple detached process
368
+ * const result = spawn_detached('node', ['daemon.js'], {cwd: '/app'});
369
+ *
370
+ * // With log file (caller handles file opening)
371
+ * import {openSync, closeSync} from 'node:fs';
372
+ * const log_fd = openSync('/var/log/daemon.log', 'a');
373
+ * const result = spawn_detached('node', ['daemon.js'], {
374
+ * cwd: '/app',
375
+ * stdio: ['ignore', log_fd, log_fd],
376
+ * });
377
+ * closeSync(log_fd);
378
+ * ```
379
+ */
380
+ export const spawn_detached = (command, args = [], options) => {
381
+ try {
382
+ const child = node_spawn_child_process(command, args, {
383
+ stdio: 'ignore',
384
+ ...options,
385
+ detached: true,
386
+ });
387
+ // Allow parent to exit independently
388
+ child.unref();
389
+ if (child.pid === undefined) {
390
+ return { ok: false, message: 'Failed to get child PID' };
391
+ }
392
+ return { ok: true, child };
393
+ }
394
+ catch (error) {
395
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
396
+ }
397
+ };
354
398
  //
355
399
  // Formatting Utilities
356
400
  //
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_util",
3
- "version": "0.48.0",
3
+ "version": "0.48.2",
4
4
  "description": "utility belt for JS",
5
5
  "glyph": "🦕",
6
6
  "logo": "logo.svg",
@@ -65,7 +65,7 @@
65
65
  "@fuzdev/fuz_css": "^0.44.1",
66
66
  "@fuzdev/fuz_ui": "^0.179.0",
67
67
  "@ryanatkn/eslint-config": "^0.9.0",
68
- "@ryanatkn/gro": "^0.188.0",
68
+ "@ryanatkn/gro": "^0.189.3",
69
69
  "@sveltejs/adapter-static": "^3.0.10",
70
70
  "@sveltejs/kit": "^2.50.1",
71
71
  "@sveltejs/package": "^2.5.7",
@@ -1,5 +1,5 @@
1
1
  import {
2
- spawn as spawn_child_process,
2
+ spawn as node_spawn_child_process,
3
3
  type SpawnOptions,
4
4
  type ChildProcess,
5
5
  } from 'node:child_process';
@@ -98,6 +98,10 @@ export interface SpawnProcessOptions extends SpawnOptions {
98
98
  * Sends SIGTERM when exceeded. A value of 0 triggers immediate SIGTERM.
99
99
  */
100
100
  timeout_ms?: number;
101
+ /**
102
+ * Custom spawn function for testing. Defaults to `node:child_process` spawn.
103
+ */
104
+ spawn_child_process?: typeof node_spawn_child_process;
101
105
  }
102
106
 
103
107
  /**
@@ -116,6 +120,11 @@ export interface DespawnOptions {
116
120
  timeout_ms?: number;
117
121
  }
118
122
 
123
+ /**
124
+ * Result of spawning a detached process.
125
+ */
126
+ export type SpawnDetachedResult = {ok: true; child: ChildProcess} | {ok: false; message: string};
127
+
119
128
  //
120
129
  // Process Handle Types
121
130
  //
@@ -251,7 +260,12 @@ export class ProcessRegistry {
251
260
  args: ReadonlyArray<string> = [],
252
261
  options?: SpawnProcessOptions,
253
262
  ): SpawnedProcess {
254
- const {signal, timeout_ms, ...spawn_options} = options ?? {};
263
+ const {
264
+ signal,
265
+ timeout_ms,
266
+ spawn_child_process = node_spawn_child_process,
267
+ ...spawn_options
268
+ } = options ?? {};
255
269
  validate_timeout_ms(timeout_ms);
256
270
  const child = spawn_child_process(command, args, {stdio: 'inherit', ...spawn_options});
257
271
 
@@ -544,6 +558,57 @@ export const attach_process_error_handler = (
544
558
  options?: Parameters<ProcessRegistry['attach_error_handler']>[0],
545
559
  ): (() => void) => process_registry_default.attach_error_handler(options);
546
560
 
561
+ /**
562
+ * Spawns a detached process that continues after parent exits.
563
+ *
564
+ * Unlike other spawn functions, this is NOT tracked in any ProcessRegistry.
565
+ * The spawned process is meant to outlive the parent (e.g., daemon processes).
566
+ *
567
+ * @param command - The command to run
568
+ * @param args - Arguments to pass to the command
569
+ * @param options - Spawn options (use `stdio` to redirect output to file descriptors)
570
+ * @returns Result with pid on success, or error message on failure
571
+ *
572
+ * @example
573
+ * ```ts
574
+ * // Simple detached process
575
+ * const result = spawn_detached('node', ['daemon.js'], {cwd: '/app'});
576
+ *
577
+ * // With log file (caller handles file opening)
578
+ * import {openSync, closeSync} from 'node:fs';
579
+ * const log_fd = openSync('/var/log/daemon.log', 'a');
580
+ * const result = spawn_detached('node', ['daemon.js'], {
581
+ * cwd: '/app',
582
+ * stdio: ['ignore', log_fd, log_fd],
583
+ * });
584
+ * closeSync(log_fd);
585
+ * ```
586
+ */
587
+ export const spawn_detached = (
588
+ command: string,
589
+ args: ReadonlyArray<string> = [],
590
+ options?: SpawnOptions,
591
+ ): SpawnDetachedResult => {
592
+ try {
593
+ const child = node_spawn_child_process(command, args, {
594
+ stdio: 'ignore',
595
+ ...options,
596
+ detached: true,
597
+ });
598
+
599
+ // Allow parent to exit independently
600
+ child.unref();
601
+
602
+ if (child.pid === undefined) {
603
+ return {ok: false, message: 'Failed to get child PID'};
604
+ }
605
+
606
+ return {ok: true, child};
607
+ } catch (error) {
608
+ return {ok: false, message: error instanceof Error ? error.message : String(error)};
609
+ }
610
+ };
611
+
547
612
  //
548
613
  // Formatting Utilities
549
614
  //