builderman 1.1.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 +398 -51
- package/dist/graph.js +0 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/modules/signal-handler.d.ts +13 -0
- package/dist/modules/signal-handler.js +38 -0
- package/dist/modules/task-executor.d.ts +25 -0
- package/dist/modules/task-executor.js +313 -0
- package/dist/modules/teardown-manager.d.ts +22 -0
- package/dist/modules/teardown-manager.js +157 -0
- package/dist/pipeline-error.d.ts +11 -0
- package/dist/pipeline-error.js +50 -0
- package/dist/pipeline.d.ts +6 -0
- package/dist/pipeline.js +207 -178
- package/dist/scheduler.d.ts +6 -3
- package/dist/scheduler.js +3 -11
- package/dist/task.d.ts +6 -0
- package/dist/task.js +11 -13
- package/dist/types.d.ts +353 -14
- package/package.json +1 -1
- package/dist/pipeline.test.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,86 +1,179 @@
|
|
|
1
|
-
#
|
|
1
|
+
# builderman
|
|
2
2
|
|
|
3
|
-
####
|
|
3
|
+
#### A dependency-aware task runner for building, developing, and orchestrating complex workflows.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
```
|
|
51
|
+
```sh
|
|
10
52
|
npm install builderman
|
|
11
53
|
```
|
|
12
54
|
|
|
13
|
-
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
14
58
|
|
|
15
59
|
```ts
|
|
16
60
|
import { task, pipeline } from "builderman"
|
|
17
61
|
|
|
18
|
-
const
|
|
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
|
-
dev: "tsc --watch",
|
|
22
103
|
build: "tsc",
|
|
104
|
+
dev: {
|
|
105
|
+
run: "tsc --watch",
|
|
106
|
+
readyWhen: (stdout) => stdout.includes("Watching for file changes."),
|
|
107
|
+
},
|
|
23
108
|
},
|
|
24
109
|
cwd: "packages/lib",
|
|
25
|
-
isReady: (stdout) => {
|
|
26
|
-
// mark this this task as ready when the process is watching for file changes
|
|
27
|
-
return stdout.includes("Watching for file changes.")
|
|
28
|
-
},
|
|
29
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:
|
|
120
|
+
|
|
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
|
|
30
125
|
|
|
31
|
-
|
|
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({
|
|
32
142
|
name: "consumer:dev",
|
|
33
143
|
commands: {
|
|
34
|
-
dev: "npm run dev",
|
|
35
144
|
build: "npm run build",
|
|
145
|
+
dev: "npm run dev",
|
|
36
146
|
},
|
|
37
147
|
cwd: "packages/consumer",
|
|
38
|
-
dependencies: [
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
await pipeline([task1, task2]).run({
|
|
42
|
-
onTaskError: (taskName, error) => {
|
|
43
|
-
console.error(`[${taskName}] Error: ${error.message}`)
|
|
44
|
-
},
|
|
45
|
-
onTaskComplete: (taskName) => {
|
|
46
|
-
console.log(`[${taskName}] Complete!`)
|
|
47
|
-
},
|
|
48
|
-
onPipelineComplete: () => {
|
|
49
|
-
console.log("All tasks complete! π")
|
|
50
|
-
},
|
|
51
|
-
onPipelineError: (error) => {
|
|
52
|
-
console.error(`Pipeline error: ${error.message}`)
|
|
53
|
-
},
|
|
148
|
+
dependencies: [libTask],
|
|
54
149
|
})
|
|
55
150
|
```
|
|
56
151
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Build complex workflows by composing tasks and pipelines together.
|
|
152
|
+
---
|
|
60
153
|
|
|
61
|
-
###
|
|
154
|
+
### Pipelines
|
|
62
155
|
|
|
63
|
-
|
|
156
|
+
A **pipeline** executes a set of tasks according to their dependency graph.
|
|
64
157
|
|
|
65
158
|
```ts
|
|
66
|
-
import {
|
|
159
|
+
import { pipeline } from "builderman"
|
|
67
160
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
161
|
+
const result = await pipeline([libTask, consumerTask]).run({
|
|
162
|
+
command: "dev",
|
|
163
|
+
onTaskBegin: (name) => {
|
|
164
|
+
console.log(`[${name}] starting`)
|
|
165
|
+
},
|
|
166
|
+
onTaskComplete: (name) => {
|
|
167
|
+
console.log(`[${name}] complete`)
|
|
168
|
+
},
|
|
76
169
|
})
|
|
77
|
-
|
|
78
|
-
await build.run()
|
|
79
170
|
```
|
|
80
171
|
|
|
81
|
-
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Pipeline Composition
|
|
82
175
|
|
|
83
|
-
|
|
176
|
+
Pipelines can be converted into tasks and composed like any other unit of work.
|
|
84
177
|
|
|
85
178
|
```ts
|
|
86
179
|
const build = pipeline([
|
|
@@ -93,15 +186,269 @@ const deploy = pipeline([
|
|
|
93
186
|
/* ... */
|
|
94
187
|
])
|
|
95
188
|
|
|
96
|
-
// Convert to tasks first
|
|
97
189
|
const buildTask = build.toTask({ name: "build" })
|
|
98
190
|
const testTask = test.toTask({ name: "test", dependencies: [buildTask] })
|
|
99
191
|
const deployTask = deploy.toTask({ name: "deploy", dependencies: [testTask] })
|
|
100
192
|
|
|
101
|
-
// Compose into final pipeline
|
|
102
193
|
const ci = pipeline([buildTask, testTask, deployTask])
|
|
194
|
+
const result = await ci.run()
|
|
195
|
+
```
|
|
196
|
+
|
|
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
|
|
202
|
+
|
|
203
|
+
**builderman pipelines never throw.**
|
|
204
|
+
|
|
205
|
+
All failures β including task errors, invalid configuration, cancellation, and process termination β are reported through a structured `RunResult`.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import { pipeline, PipelineError } from "builderman"
|
|
209
|
+
|
|
210
|
+
const result = await pipeline([libTask, consumerTask]).run()
|
|
211
|
+
|
|
212
|
+
if (!result.ok) {
|
|
213
|
+
switch (result.error.code) {
|
|
214
|
+
case PipelineError.Aborted:
|
|
215
|
+
console.error("Pipeline was cancelled")
|
|
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()
|
|
103
243
|
|
|
104
|
-
|
|
244
|
+
const runPromise = pipeline([libTask, consumerTask]).run({
|
|
245
|
+
signal: controller.signal,
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// Cancel after 5 seconds
|
|
249
|
+
setTimeout(() => {
|
|
250
|
+
controller.abort()
|
|
251
|
+
}, 5000)
|
|
252
|
+
|
|
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}`)
|
|
258
|
+
}
|
|
105
259
|
```
|
|
106
260
|
|
|
107
|
-
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Teardown
|
|
264
|
+
|
|
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.
|
|
268
|
+
|
|
269
|
+
### Basic Teardown
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const dbTask = task({
|
|
273
|
+
name: "database",
|
|
274
|
+
commands: {
|
|
275
|
+
dev: {
|
|
276
|
+
run: "docker-compose up",
|
|
277
|
+
teardown: "docker-compose down",
|
|
278
|
+
},
|
|
279
|
+
build: "echo build",
|
|
280
|
+
},
|
|
281
|
+
cwd: ".",
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
### Teardown Callbacks
|
|
288
|
+
|
|
289
|
+
You can observe teardown execution using callbacks. Teardown failures do **not** cause the pipeline to fail β they are best-effort cleanup operations.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const result = await pipeline([dbTask]).run({
|
|
293
|
+
onTaskTeardown: (taskName) => {
|
|
294
|
+
console.log(`[${taskName}] starting teardown`)
|
|
295
|
+
},
|
|
296
|
+
onTaskTeardownError: (taskName, error) => {
|
|
297
|
+
console.error(`[${taskName}] teardown failed: ${error.message}`)
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Teardown results are recorded in task statistics.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### Teardown Execution Rules
|
|
307
|
+
|
|
308
|
+
Teardowns run when:
|
|
309
|
+
|
|
310
|
+
- The command entered the running state
|
|
311
|
+
- The pipeline completes successfully
|
|
312
|
+
- The pipeline fails after tasks have started
|
|
313
|
+
|
|
314
|
+
Teardowns do **not** run when:
|
|
315
|
+
|
|
316
|
+
- The task was skipped
|
|
317
|
+
- The task failed before starting (spawn error)
|
|
318
|
+
- The pipeline never began execution
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Skipping Tasks
|
|
323
|
+
|
|
324
|
+
If a task does not define a command for the current mode, it is **skipped** by default.
|
|
325
|
+
|
|
326
|
+
Skipped tasks:
|
|
327
|
+
|
|
328
|
+
- Participate in the dependency graph
|
|
329
|
+
- Resolve immediately
|
|
330
|
+
- Unblock dependent tasks
|
|
331
|
+
- Do not execute commands or teardowns
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const dbTask = task({
|
|
335
|
+
name: "database",
|
|
336
|
+
commands: {
|
|
337
|
+
dev: "docker-compose up",
|
|
338
|
+
},
|
|
339
|
+
cwd: ".",
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
const apiTask = task({
|
|
343
|
+
name: "api",
|
|
344
|
+
commands: {
|
|
345
|
+
dev: "npm run dev",
|
|
346
|
+
build: "npm run build",
|
|
347
|
+
},
|
|
348
|
+
cwd: ".",
|
|
349
|
+
dependencies: [dbTask],
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
const result = await pipeline([dbTask, apiTask]).run({
|
|
353
|
+
command: "build",
|
|
354
|
+
onTaskSkipped: (taskName, mode) => {
|
|
355
|
+
console.log(`[${taskName}] skipped (no "${mode}" command)`)
|
|
356
|
+
},
|
|
357
|
+
})
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
### Strict Mode
|
|
363
|
+
|
|
364
|
+
In **strict mode**, missing commands cause the pipeline to fail. This is useful for CI and release pipelines.
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
const result = await pipeline([dbTask, apiTask]).run({
|
|
368
|
+
command: "build",
|
|
369
|
+
strict: true,
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
if (!result.ok) {
|
|
373
|
+
console.error("Pipeline failed in strict mode:", result.error.message)
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
### Task-Level Skip Override
|
|
380
|
+
|
|
381
|
+
Tasks may explicitly allow skipping, even when strict mode is enabled.
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
const dbTask = task({
|
|
385
|
+
name: "database",
|
|
386
|
+
commands: {
|
|
387
|
+
dev: "docker-compose up",
|
|
388
|
+
},
|
|
389
|
+
cwd: ".",
|
|
390
|
+
allowSkip: true,
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
const result = await pipeline([dbTask]).run({
|
|
394
|
+
command: "build",
|
|
395
|
+
strict: true,
|
|
396
|
+
})
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Execution Statistics
|
|
402
|
+
|
|
403
|
+
Every pipeline run returns detailed execution statistics.
|
|
404
|
+
|
|
405
|
+
### Pipeline Statistics
|
|
406
|
+
|
|
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)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
### Task Statistics
|
|
421
|
+
|
|
422
|
+
Each task provides detailed per-task data:
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
for (const task of Object.values(result.stats.tasks)) {
|
|
426
|
+
console.log(task.name, task.status)
|
|
427
|
+
console.log(task.durationMs)
|
|
428
|
+
|
|
429
|
+
if (task.status === "failed") {
|
|
430
|
+
console.error(task.error?.message)
|
|
431
|
+
console.error(task.exitCode)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (task.teardown) {
|
|
435
|
+
console.log("Teardown:", task.teardown.status)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## When Should I Use builderman?
|
|
443
|
+
|
|
444
|
+
**builderman** is a good fit when:
|
|
445
|
+
|
|
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
|
|
450
|
+
|
|
451
|
+
It may be overkill if:
|
|
452
|
+
|
|
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
2
|
export { pipeline } from "./pipeline.js";
|
|
3
|
-
export
|
|
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
|
@@ -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;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a signal handler for pipeline execution.
|
|
3
|
+
* Handles process termination signals (SIGINT, SIGTERM, etc.) and abort signals.
|
|
4
|
+
*/
|
|
5
|
+
export function createSignalHandler({ abortSignal, onAborted, onProcessTerminated, }) {
|
|
6
|
+
// Handle termination signals
|
|
7
|
+
const processTerminationListenerCleanups = [
|
|
8
|
+
"SIGINT",
|
|
9
|
+
"SIGTERM",
|
|
10
|
+
"SIGQUIT",
|
|
11
|
+
"SIGBREAK",
|
|
12
|
+
].map((sig) => {
|
|
13
|
+
const handleSignal = () => {
|
|
14
|
+
onProcessTerminated(`Received ${sig}`);
|
|
15
|
+
};
|
|
16
|
+
process.once(sig, handleSignal);
|
|
17
|
+
return () => {
|
|
18
|
+
process.removeListener(sig, handleSignal);
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
// Handle abort signal if provided
|
|
22
|
+
let signalCleanup = null;
|
|
23
|
+
if (abortSignal) {
|
|
24
|
+
abortSignal.addEventListener("abort", onAborted);
|
|
25
|
+
signalCleanup = () => {
|
|
26
|
+
abortSignal.removeEventListener("abort", onAborted);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
/**
|
|
31
|
+
* Cleans up all signal listeners.
|
|
32
|
+
*/
|
|
33
|
+
cleanup() {
|
|
34
|
+
processTerminationListenerCleanups.forEach((cleanup) => cleanup());
|
|
35
|
+
signalCleanup?.();
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type ChildProcess } from "node:child_process";
|
|
2
|
+
import { PipelineError } from "../pipeline-error.js";
|
|
3
|
+
import type { Task, Pipeline, PipelineRunConfig, TaskGraph, TaskStats } from "../types.js";
|
|
4
|
+
import type { SchedulerInput } from "../scheduler.js";
|
|
5
|
+
import type { TeardownManager } from "./teardown-manager.js";
|
|
6
|
+
export interface TaskExecutorConfig {
|
|
7
|
+
spawn: typeof import("node:child_process").spawn;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
config?: PipelineRunConfig;
|
|
10
|
+
graph: TaskGraph;
|
|
11
|
+
runningTasks: Map<string, ChildProcess>;
|
|
12
|
+
runningPipelines: Map<string, {
|
|
13
|
+
stop: () => void;
|
|
14
|
+
}>;
|
|
15
|
+
teardownManager: TeardownManager;
|
|
16
|
+
pipelineTasksCache: WeakMap<Pipeline, Task[]>;
|
|
17
|
+
failPipeline: (error: PipelineError) => Promise<void>;
|
|
18
|
+
advanceScheduler: (input?: SchedulerInput) => void;
|
|
19
|
+
updateTaskStatus: (taskId: string, updates: Partial<TaskStats>) => void;
|
|
20
|
+
taskStats: Map<string, TaskStats>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Executes a task (either a regular task or a nested pipeline).
|
|
24
|
+
*/
|
|
25
|
+
export declare function executeTask(task: Task, executorConfig: TaskExecutorConfig): void;
|