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 +290 -197
- package/dist/graph.js +0 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- 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 +0 -12
- package/dist/pipeline.js +164 -481
- package/dist/scheduler.d.ts +4 -4
- package/dist/scheduler.js +3 -3
- package/dist/task.d.ts +0 -5
- package/dist/task.js +5 -17
- package/dist/types.d.ts +208 -25
- package/package.json +1 -1
- package/dist/pipeline.test.d.ts +0 -1
package/README.md
CHANGED
|
@@ -1,129 +1,270 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
####
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
```
|
|
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
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
|
-
|
|
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: [
|
|
148
|
+
dependencies: [libTask],
|
|
42
149
|
})
|
|
150
|
+
```
|
|
43
151
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
console.
|
|
166
|
+
onTaskComplete: (name) => {
|
|
167
|
+
console.log(`[${name}] complete`)
|
|
58
168
|
},
|
|
59
169
|
})
|
|
60
170
|
```
|
|
61
171
|
|
|
62
|
-
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Pipeline Composition
|
|
63
175
|
|
|
64
|
-
|
|
176
|
+
Pipelines can be converted into tasks and composed like any other unit of work.
|
|
65
177
|
|
|
66
178
|
```ts
|
|
67
|
-
|
|
179
|
+
const build = pipeline([
|
|
180
|
+
/* ... */
|
|
181
|
+
])
|
|
182
|
+
const test = pipeline([
|
|
183
|
+
/* ... */
|
|
184
|
+
])
|
|
185
|
+
const deploy = pipeline([
|
|
186
|
+
/* ... */
|
|
187
|
+
])
|
|
68
188
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
210
|
+
const result = await pipeline([libTask, consumerTask]).run()
|
|
100
211
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
248
|
+
// Cancel after 5 seconds
|
|
111
249
|
setTimeout(() => {
|
|
112
|
-
|
|
250
|
+
controller.abort()
|
|
113
251
|
}, 5000)
|
|
114
252
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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
|
|
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}]
|
|
294
|
+
console.log(`[${taskName}] starting teardown`)
|
|
152
295
|
},
|
|
153
296
|
onTaskTeardownError: (taskName, error) => {
|
|
154
|
-
console.error(`[${taskName}]
|
|
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
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
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
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
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
|
-
|
|
200
|
-
await pipeline([db, api]).run()
|
|
201
|
-
```
|
|
320
|
+
---
|
|
202
321
|
|
|
203
322
|
## Skipping Tasks
|
|
204
323
|
|
|
205
|
-
|
|
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
|
-
|
|
326
|
+
Skipped tasks:
|
|
210
327
|
|
|
211
|
-
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
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],
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
377
|
+
---
|
|
258
378
|
|
|
259
|
-
|
|
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,
|
|
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,
|
|
275
|
-
// dbTask will still be skipped because allowSkip: true
|
|
395
|
+
strict: true,
|
|
276
396
|
})
|
|
277
397
|
```
|
|
278
398
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
When a pipeline is converted to a task, skip behavior is preserved:
|
|
399
|
+
---
|
|
282
400
|
|
|
283
|
-
|
|
284
|
-
- If **some** run, some skip β outer task is completed
|
|
285
|
-
- If **any** fail β outer task fails
|
|
401
|
+
## Execution Statistics
|
|
286
402
|
|
|
287
|
-
|
|
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
|
-
|
|
405
|
+
### Pipeline Statistics
|
|
294
406
|
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
Build complex workflows by composing tasks and pipelines together.
|
|
418
|
+
---
|
|
302
419
|
|
|
303
|
-
### Task
|
|
420
|
+
### Task Statistics
|
|
304
421
|
|
|
305
|
-
|
|
422
|
+
Each task provides detailed per-task data:
|
|
306
423
|
|
|
307
424
|
```ts
|
|
308
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
434
|
+
if (task.teardown) {
|
|
435
|
+
console.log("Teardown:", task.teardown.status)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
333
438
|
```
|
|
334
439
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
Convert pipelines to tasks and compose them with explicit dependencies:
|
|
440
|
+
---
|
|
338
441
|
|
|
339
|
-
|
|
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
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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
|
-
|
|
359
|
-
```
|
|
451
|
+
It may be overkill if:
|
|
360
452
|
|
|
361
|
-
|
|
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
|
|
3
|
-
export
|
|
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
|
@@ -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;
|