builderman 1.2.0 β†’ 1.3.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/README.md CHANGED
@@ -1,129 +1,270 @@
1
- # **builderman**
2
-
3
- #### _A simple task runner for building and developing projects._
4
-
5
- <br />
1
+ # builderman
2
+
3
+ #### A dependency-aware task runner for building, developing, and orchestrating complex workflows.
4
+
5
+ **builderman** lets you define tasks with explicit dependencies, lifecycle hooks, and multiple execution modes (`dev`, `build`, `deploy`, etc.), then compose them into pipelines that run **deterministically**, **observably**, and **safely**.
6
+
7
+ It is designed for monorepos, long-running development processes, and CI/CD pipelines where **cleanup, cancellation, and failure handling matter**.
8
+
9
+ ---
10
+
11
+ ## Table of Contents
12
+
13
+ - [Key Features](#key-features)
14
+ - [Installation](#installation)
15
+ - [Quick Start](#quick-start)
16
+ - [Core Concepts](#core-concepts)
17
+ - [Tasks](#tasks)
18
+ - [Commands & Modes](#commands--modes)
19
+ - [Dependencies](#dependencies)
20
+ - [Pipelines](#pipelines)
21
+ - [Pipeline Composition](#pipeline-composition)
22
+ - [Error Handling Guarantees](#error-handling-guarantees)
23
+ - [Cancellation](#cancellation)
24
+ - [Teardown](#teardown)
25
+ - [Basic Teardown](#basic-teardown)
26
+ - [Teardown Callbacks](#teardown-callbacks)
27
+ - [Teardown Execution Rules](#teardown-execution-rules)
28
+ - [Skipping Tasks](#skipping-tasks)
29
+ - [Strict Mode](#strict-mode)
30
+ - [Task-Level Skip Override](#task-level-skip-override)
31
+ - [Execution Statistics](#execution-statistics)
32
+ - [Pipeline Statistics](#pipeline-statistics)
33
+ - [Task Statistics](#task-statistics)
34
+ - [When Should I Use builderman?](#when-should-i-use-builderman)
35
+
36
+ ## Key Features
37
+
38
+ - 🧩 **Explicit dependency graph** β€” tasks run only when their dependencies are satisfied
39
+ - πŸ” **Multi-mode commands** β€” `dev`, `build`, `deploy`, or any custom mode
40
+ - ⏳ **Readiness detection** β€” wait for long-running processes to become β€œready”
41
+ - 🧹 **Guaranteed teardown** β€” automatic cleanup in reverse dependency order
42
+ - πŸ›‘ **Cancellation support** β€” abort pipelines using `AbortSignal`
43
+ - πŸ“Š **Rich execution statistics** β€” always available, even on failure
44
+ - ❌ **Never throws** β€” failures are returned as structured results
45
+ - 🧱 **Composable pipelines** β€” pipelines can be converted into tasks
46
+
47
+ ---
6
48
 
7
49
  ## Installation
8
50
 
9
- ```bash
51
+ ```sh
10
52
  npm install builderman
11
53
  ```
12
54
 
13
- ## Usage
55
+ ---
56
+
57
+ ## Quick Start
14
58
 
15
59
  ```ts
16
60
  import { task, pipeline } from "builderman"
17
61
 
18
- const task1 = task({
62
+ const build = task({
63
+ name: "build",
64
+ commands: { build: "tsc" },
65
+ cwd: ".",
66
+ })
67
+
68
+ const test = task({
69
+ name: "test",
70
+ commands: { build: "npm test" },
71
+ cwd: ".",
72
+ dependencies: [build],
73
+ })
74
+
75
+ const result = await pipeline([build, test]).run()
76
+
77
+ if (!result.ok) {
78
+ console.error("Pipeline failed:", result.error.message)
79
+ }
80
+ ```
81
+
82
+ This defines a simple dependency graph where `test` runs only after `build` completes successfully.
83
+
84
+ ---
85
+
86
+ ## Core Concepts
87
+
88
+ ### Tasks
89
+
90
+ A **task** represents a unit of work. Each task:
91
+
92
+ - Has a unique name
93
+ - Defines commands for one or more modes
94
+ - May depend on other tasks
95
+ - May register teardown logic
96
+
97
+ ```ts
98
+ import { task } from "builderman"
99
+
100
+ const libTask = task({
19
101
  name: "lib:build",
20
102
  commands: {
21
103
  build: "tsc",
22
104
  dev: {
23
105
  run: "tsc --watch",
24
- readyWhen: (stdout) => {
25
- // mark this task as ready when the process is watching for file changes
26
- return stdout.includes("Watching for file changes.")
27
- },
106
+ readyWhen: (stdout) => stdout.includes("Watching for file changes."),
28
107
  },
29
108
  },
30
109
  cwd: "packages/lib",
31
110
  })
111
+ ```
112
+
113
+ ---
114
+
115
+ ### Commands & Modes
116
+
117
+ Each task can define commands for different **modes** (for example `dev`, `build`, `deploy`).
118
+
119
+ When running a pipeline:
32
120
 
33
- const task2 = task({
121
+ - If `command` is provided, that mode is used
122
+ - Otherwise:
123
+ - `"build"` is used when `NODE_ENV === "production"`
124
+ - `"dev"` is used in all other cases
125
+
126
+ Commands may be:
127
+
128
+ - A string (executed directly), or
129
+ - An object with:
130
+ - `run`: the command to execute
131
+ - `readyWhen`: a predicate that marks the task as ready
132
+ - `teardown`: cleanup logic to run after completion
133
+
134
+ ---
135
+
136
+ ### Dependencies
137
+
138
+ Tasks may depend on other tasks. A task will not start until all its dependencies have completed (or been skipped).
139
+
140
+ ```ts
141
+ const consumerTask = task({
34
142
  name: "consumer:dev",
35
143
  commands: {
36
144
  build: "npm run build",
37
145
  dev: "npm run dev",
38
- deploy: "npm run deploy",
39
146
  },
40
147
  cwd: "packages/consumer",
41
- dependencies: [task1],
148
+ dependencies: [libTask],
42
149
  })
150
+ ```
43
151
 
44
- await pipeline([task1, task2]).run({
45
- // default command is "build" if process.NODE_ENV is "production", otherwise "dev".
46
- command: "deploy",
47
- onTaskBegin: (taskName) => {
48
- console.log(`[${taskName}] Starting...`)
49
- },
50
- onTaskComplete: (taskName) => {
51
- console.log(`[${taskName}] Complete!`)
52
- },
53
- onPipelineComplete: () => {
54
- console.log("All tasks complete! πŸŽ‰")
152
+ ---
153
+
154
+ ### Pipelines
155
+
156
+ A **pipeline** executes a set of tasks according to their dependency graph.
157
+
158
+ ```ts
159
+ import { pipeline } from "builderman"
160
+
161
+ const result = await pipeline([libTask, consumerTask]).run({
162
+ command: "dev",
163
+ onTaskBegin: (name) => {
164
+ console.log(`[${name}] starting`)
55
165
  },
56
- onPipelineError: (error) => {
57
- console.error(`Pipeline error: ${error.message}`)
166
+ onTaskComplete: (name) => {
167
+ console.log(`[${name}] complete`)
58
168
  },
59
169
  })
60
170
  ```
61
171
 
62
- ## Error Handling
172
+ ---
173
+
174
+ ### Pipeline Composition
63
175
 
64
- Pipeline errors are provided as `PipelineError` instances with error codes for easier handling:
176
+ Pipelines can be converted into tasks and composed like any other unit of work.
65
177
 
66
178
  ```ts
67
- import { pipeline, PipelineError } from "builderman"
179
+ const build = pipeline([
180
+ /* ... */
181
+ ])
182
+ const test = pipeline([
183
+ /* ... */
184
+ ])
185
+ const deploy = pipeline([
186
+ /* ... */
187
+ ])
68
188
 
69
- await pipeline([task1, task2]).run({
70
- onPipelineError: (error) => {
71
- switch (error.code) {
72
- case PipelineError.Aborted:
73
- console.error("Pipeline was cancelled")
74
- break
75
- case PipelineError.TaskFailed:
76
- console.error(`Task failed: ${error.message}`)
77
- break
78
- case PipelineError.ProcessTerminated:
79
- console.error("Process was terminated")
80
- break
81
- case PipelineError.InvalidTask:
82
- console.error(`Invalid task configuration: ${error.message}`)
83
- break
84
- case PipelineError.InvalidSignal:
85
- console.error("Invalid abort signal")
86
- break
87
- }
88
- },
89
- })
189
+ const buildTask = build.toTask({ name: "build" })
190
+ const testTask = test.toTask({ name: "test", dependencies: [buildTask] })
191
+ const deployTask = deploy.toTask({ name: "deploy", dependencies: [testTask] })
192
+
193
+ const ci = pipeline([buildTask, testTask, deployTask])
194
+ const result = await ci.run()
90
195
  ```
91
196
 
92
- ## Cancellation
197
+ When a pipeline is converted to a task, it becomes a **single node** in the dependency graph. The nested pipeline must fully complete before dependents can start.
198
+
199
+ ---
200
+
201
+ ## Error Handling Guarantees
93
202
 
94
- You can cancel a running pipeline by providing an `AbortSignal`:
203
+ **builderman pipelines never throw.**
204
+
205
+ All failures β€” including task errors, invalid configuration, cancellation, and process termination β€” are reported through a structured `RunResult`.
95
206
 
96
207
  ```ts
97
208
  import { pipeline, PipelineError } from "builderman"
98
209
 
99
- const abortController = new AbortController()
210
+ const result = await pipeline([libTask, consumerTask]).run()
100
211
 
101
- const runPromise = pipeline([task1, task2]).run({
102
- signal: abortController.signal,
103
- onPipelineError: (error) => {
104
- if (error.code === PipelineError.Aborted) {
212
+ if (!result.ok) {
213
+ switch (result.error.code) {
214
+ case PipelineError.Aborted:
105
215
  console.error("Pipeline was cancelled")
106
- }
107
- },
216
+ break
217
+ case PipelineError.TaskFailed:
218
+ console.error(`Task failed: ${result.error.message}`)
219
+ break
220
+ case PipelineError.ProcessTerminated:
221
+ console.error("Process was terminated")
222
+ break
223
+ case PipelineError.InvalidTask:
224
+ console.error(`Invalid task configuration: ${result.error.message}`)
225
+ break
226
+ case PipelineError.InvalidSignal:
227
+ console.error("Invalid abort signal")
228
+ break
229
+ }
230
+ }
231
+ ```
232
+
233
+ Execution statistics are **always available**, even on failure.
234
+
235
+ ---
236
+
237
+ ## Cancellation
238
+
239
+ You can cancel a running pipeline using an `AbortSignal`.
240
+
241
+ ```ts
242
+ const controller = new AbortController()
243
+
244
+ const runPromise = pipeline([libTask, consumerTask]).run({
245
+ signal: controller.signal,
108
246
  })
109
247
 
110
- // Cancel the pipeline after 5 seconds
248
+ // Cancel after 5 seconds
111
249
  setTimeout(() => {
112
- abortController.abort()
250
+ controller.abort()
113
251
  }, 5000)
114
252
 
115
- try {
116
- await runPromise
117
- } catch (error) {
118
- if (error instanceof PipelineError && error.code === PipelineError.Aborted) {
119
- // Pipeline was cancelled
120
- }
253
+ const result = await runPromise
254
+
255
+ if (!result.ok && result.error.code === PipelineError.Aborted) {
256
+ console.error("Pipeline was cancelled")
257
+ console.log(`Tasks still running: ${result.stats.summary.running}`)
121
258
  }
122
259
  ```
123
260
 
261
+ ---
262
+
124
263
  ## Teardown
125
264
 
126
- Tasks can specify teardown commands that run automatically when the task completes or fails. Teardowns are executed in reverse dependency order (dependents before dependencies) to ensure proper cleanup.
265
+ Tasks may specify teardown commands that run automatically when a task completes or fails.
266
+
267
+ Teardowns are executed **in reverse dependency order** (dependents before dependencies) to ensure safe cleanup.
127
268
 
128
269
  ### Basic Teardown
129
270
 
@@ -141,86 +282,59 @@ const dbTask = task({
141
282
  })
142
283
  ```
143
284
 
285
+ ---
286
+
144
287
  ### Teardown Callbacks
145
288
 
146
- You can monitor teardown execution with callbacks. Note that teardown failures do not cause the pipeline to fail - they are fire-and-forget cleanup operations:
289
+ You can observe teardown execution using callbacks. Teardown failures do **not** cause the pipeline to fail β€” they are best-effort cleanup operations.
147
290
 
148
291
  ```ts
149
- await pipeline([dbTask]).run({
292
+ const result = await pipeline([dbTask]).run({
150
293
  onTaskTeardown: (taskName) => {
151
- console.log(`[${taskName}] Starting teardown...`)
294
+ console.log(`[${taskName}] starting teardown`)
152
295
  },
153
296
  onTaskTeardownError: (taskName, error) => {
154
- console.error(`[${taskName}] Teardown failed: ${error.message}`)
155
- // error is a regular Error instance (not a PipelineError)
156
- // Teardown failures do not affect pipeline success/failure
297
+ console.error(`[${taskName}] teardown failed: ${error.message}`)
157
298
  },
158
299
  })
159
300
  ```
160
301
 
302
+ Teardown results are recorded in task statistics.
303
+
304
+ ---
305
+
161
306
  ### Teardown Execution Rules
162
307
 
163
308
  Teardowns run when:
164
309
 
165
- - βœ… The command entered the running state (regardless of success or failure)
166
- - βœ… The pipeline completes successfully
167
- - βœ… The pipeline fails after tasks have started
310
+ - The command entered the running state
311
+ - The pipeline completes successfully
312
+ - The pipeline fails after tasks have started
168
313
 
169
314
  Teardowns do **not** run when:
170
315
 
171
- - ❌ The task was skipped (no command for the current mode)
172
- - ❌ The task failed before starting (spawn error)
173
- - ❌ The pipeline never began execution
174
-
175
- ### Reverse Dependency Order
176
-
177
- Teardowns execute in reverse dependency order to ensure dependents are cleaned up before their dependencies:
178
-
179
- ```ts
180
- const db = task({
181
- name: "db",
182
- commands: {
183
- dev: { run: "docker-compose up", teardown: "docker-compose down" },
184
- build: "echo build",
185
- },
186
- cwd: ".",
187
- })
188
-
189
- const api = task({
190
- name: "api",
191
- commands: {
192
- dev: { run: "npm run dev", teardown: "echo stopping api" },
193
- build: "echo build",
194
- },
195
- cwd: ".",
196
- dependencies: [db], // api depends on db
197
- })
316
+ - The task was skipped
317
+ - The task failed before starting (spawn error)
318
+ - The pipeline never began execution
198
319
 
199
- // Teardown order: api first, then db
200
- await pipeline([db, api]).run()
201
- ```
320
+ ---
202
321
 
203
322
  ## Skipping Tasks
204
323
 
205
- Tasks can be automatically skipped when they don't have a command for the current mode. This is useful for multi-mode pipelines where some tasks are only relevant in certain contexts.
206
-
207
- ### Default Behavior
324
+ If a task does not define a command for the current mode, it is **skipped** by default.
208
325
 
209
- If a task has no command for the current mode, it is **skipped**:
326
+ Skipped tasks:
210
327
 
211
- - βœ… The task participates in the dependency graph
212
- - βœ… The task resolves immediately (satisfies dependencies)
213
- - βœ… Dependents are unblocked
214
- - ❌ No command is executed
215
- - ❌ No teardown is registered
216
- - ❌ No readiness is waited for
328
+ - Participate in the dependency graph
329
+ - Resolve immediately
330
+ - Unblock dependent tasks
331
+ - Do not execute commands or teardowns
217
332
 
218
333
  ```ts
219
334
  const dbTask = task({
220
335
  name: "database",
221
336
  commands: {
222
337
  dev: "docker-compose up",
223
- // No build command - will be skipped in build mode
224
338
  },
225
339
  cwd: ".",
226
340
  })
@@ -232,130 +346,109 @@ const apiTask = task({
232
346
  build: "npm run build",
233
347
  },
234
348
  cwd: ".",
235
- dependencies: [dbTask], // dbTask will be skipped, but apiTask will still run
349
+ dependencies: [dbTask],
236
350
  })
237
351
 
238
- await pipeline([dbTask, apiTask]).run({
352
+ const result = await pipeline([dbTask, apiTask]).run({
239
353
  command: "build",
240
354
  onTaskSkipped: (taskName, mode) => {
241
- console.log(`[${taskName}] skipped (no command for mode "${mode}")`)
355
+ console.log(`[${taskName}] skipped (no "${mode}" command)`)
242
356
  },
243
357
  })
244
358
  ```
245
359
 
360
+ ---
361
+
246
362
  ### Strict Mode
247
363
 
248
- In strict mode, missing commands cause the pipeline to fail. Use this for CI/release pipelines where every task is expected to participate:
364
+ In **strict mode**, missing commands cause the pipeline to fail. This is useful for CI and release pipelines.
249
365
 
250
366
  ```ts
251
- await pipeline([dbTask, apiTask]).run({
367
+ const result = await pipeline([dbTask, apiTask]).run({
252
368
  command: "build",
253
- strict: true, // Missing commands will cause pipeline to fail
369
+ strict: true,
254
370
  })
371
+
372
+ if (!result.ok) {
373
+ console.error("Pipeline failed in strict mode:", result.error.message)
374
+ }
255
375
  ```
256
376
 
257
- ### Task-Level Override
377
+ ---
258
378
 
259
- Even with global strict mode, you can explicitly allow a task to be skipped:
379
+ ### Task-Level Skip Override
380
+
381
+ Tasks may explicitly allow skipping, even when strict mode is enabled.
260
382
 
261
383
  ```ts
262
384
  const dbTask = task({
263
385
  name: "database",
264
386
  commands: {
265
387
  dev: "docker-compose up",
266
- // No build command, but explicitly allowed to skip
267
388
  },
268
389
  cwd: ".",
269
- allowSkip: true, // Explicitly allow skipping even in strict mode
390
+ allowSkip: true,
270
391
  })
271
392
 
272
- await pipeline([dbTask]).run({
393
+ const result = await pipeline([dbTask]).run({
273
394
  command: "build",
274
- strict: true, // Global strict mode
275
- // dbTask will still be skipped because allowSkip: true
395
+ strict: true,
276
396
  })
277
397
  ```
278
398
 
279
- ### Nested Pipeline Behavior
280
-
281
- When a pipeline is converted to a task, skip behavior is preserved:
399
+ ---
282
400
 
283
- - If **all** inner tasks are skipped β†’ outer task is skipped
284
- - If **some** run, some skip β†’ outer task is completed
285
- - If **any** fail β†’ outer task fails
401
+ ## Execution Statistics
286
402
 
287
- ```ts
288
- const innerPipeline = pipeline([
289
- task({ name: "inner1", commands: { dev: "..." }, cwd: "." }),
290
- task({ name: "inner2", commands: { dev: "..." }, cwd: "." }),
291
- ])
403
+ Every pipeline run returns detailed execution statistics.
292
404
 
293
- const outerTask = innerPipeline.toTask({ name: "outer" })
405
+ ### Pipeline Statistics
294
406
 
295
- // If all inner tasks are skipped in build mode, outer task is also skipped
296
- await pipeline([outerTask]).run({ command: "build" })
407
+ ```ts
408
+ console.log(result.stats.status) // "success" | "failed" | "aborted"
409
+ console.log(result.stats.command) // Executed mode
410
+ console.log(result.stats.durationMs) // Total execution time
411
+ console.log(result.stats.summary.total)
412
+ console.log(result.stats.summary.completed)
413
+ console.log(result.stats.summary.failed)
414
+ console.log(result.stats.summary.skipped)
415
+ console.log(result.stats.summary.running)
297
416
  ```
298
417
 
299
- ## Pipeline Composition
300
-
301
- Build complex workflows by composing tasks and pipelines together.
418
+ ---
302
419
 
303
- ### Task Chaining
420
+ ### Task Statistics
304
421
 
305
- Chain tasks together using `andThen()` to create a pipeline that will run the tasks in order, automatically adding the previous task as a dependency:
422
+ Each task provides detailed per-task data:
306
423
 
307
424
  ```ts
308
- import { task } from "builderman"
425
+ for (const task of Object.values(result.stats.tasks)) {
426
+ console.log(task.name, task.status)
427
+ console.log(task.durationMs)
309
428
 
310
- const build = task({
311
- name: "compile",
312
- commands: {
313
- build: "tsc",
314
- dev: {
315
- run: "tsc --watch",
316
- readyWhen: (output) => output.includes("Watching for file changes."),
317
- },
318
- },
319
- cwd: "packages/lib",
320
- }).andThen({
321
- name: "bundle",
322
- commands: {
323
- build: "rollup",
324
- dev: {
325
- run: "rollup --watch",
326
- readyWhen: (output) => output.includes("Watching for file changes."),
327
- },
328
- },
329
- cwd: "packages/lib",
330
- })
429
+ if (task.status === "failed") {
430
+ console.error(task.error?.message)
431
+ console.error(task.exitCode)
432
+ }
331
433
 
332
- await build.run()
434
+ if (task.teardown) {
435
+ console.log("Teardown:", task.teardown.status)
436
+ }
437
+ }
333
438
  ```
334
439
 
335
- ### Composing Pipelines as Tasks
336
-
337
- Convert pipelines to tasks and compose them with explicit dependencies:
440
+ ---
338
441
 
339
- ```ts
340
- const build = pipeline([
341
- /* ... */
342
- ])
343
- const test = pipeline([
344
- /* ... */
345
- ])
346
- const deploy = pipeline([
347
- /* ... */
348
- ])
442
+ ## When Should I Use builderman?
349
443
 
350
- // Convert to tasks first
351
- const buildTask = build.toTask({ name: "build" })
352
- const testTask = test.toTask({ name: "test", dependencies: [buildTask] })
353
- const deployTask = deploy.toTask({ name: "deploy", dependencies: [testTask] })
444
+ **builderman** is a good fit when:
354
445
 
355
- // Compose into final pipeline
356
- const ci = pipeline([buildTask, testTask, deployTask])
446
+ - You have dependent tasks that must run in a strict order
447
+ - You run long-lived dev processes that need readiness detection
448
+ - Cleanup matters (databases, containers, servers)
449
+ - You want structured results instead of log-scraping
357
450
 
358
- await ci.run()
359
- ```
451
+ It may be overkill if:
360
452
 
361
- **Note:** When a pipeline is converted to a task, it becomes a single unit in the dependency graph. The nested pipeline will execute completely before any dependent tasks can start.
453
+ - You only need a few linear npm scripts
454
+ - You do not need dependency graphs or teardown guarantees
package/dist/graph.js CHANGED
@@ -1,8 +1,6 @@
1
1
  import { $TASK_INTERNAL } from "./constants.js";
2
- import { validateTasks } from "./util.js";
3
2
  export function createTaskGraph(tasks) {
4
3
  const nodes = new Map();
5
- validateTasks(tasks);
6
4
  // Create nodes for all tasks
7
5
  for (const task of tasks) {
8
6
  const { id: taskId } = task[$TASK_INTERNAL];
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { task } from "./task.js";
2
- export { pipeline, PipelineError } from "./pipeline.js";
3
- export type { Task, Pipeline, TaskConfig, Command, CommandConfig, Commands, PipelineRunConfig, PipelineTaskConfig, } from "./types.js";
2
+ export { pipeline } from "./pipeline.js";
3
+ export { PipelineError, type PipelineErrorCode } from "./pipeline-error.js";
4
+ export type { Task, Pipeline, TaskConfig, Command, CommandConfig, Commands, PipelineRunConfig, PipelineTaskConfig, RunResult, PipelineStats, TaskStats, TaskStatus, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { task } from "./task.js";
2
- export { pipeline, PipelineError } from "./pipeline.js";
2
+ export { pipeline } from "./pipeline.js";
3
+ export { PipelineError } from "./pipeline-error.js";
@@ -0,0 +1,13 @@
1
+ export interface SignalHandlerConfig {
2
+ abortSignal?: AbortSignal;
3
+ onProcessTerminated: (message: string) => void;
4
+ onAborted: () => void;
5
+ }
6
+ export interface SignalHandler {
7
+ cleanup(): void;
8
+ }
9
+ /**
10
+ * Creates a signal handler for pipeline execution.
11
+ * Handles process termination signals (SIGINT, SIGTERM, etc.) and abort signals.
12
+ */
13
+ export declare function createSignalHandler({ abortSignal, onAborted, onProcessTerminated, }: SignalHandlerConfig): SignalHandler;