builderman 1.2.0 → 1.4.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.
@@ -1,17 +1,17 @@
1
1
  import type { TaskGraph } from "./types.js";
2
2
  export type SchedulerInput = {
3
3
  type: "complete";
4
- taskId: number;
4
+ taskId: string;
5
5
  } | {
6
6
  type: "ready";
7
- taskId: number;
7
+ taskId: string;
8
8
  } | {
9
9
  type: "skip";
10
- taskId: number;
10
+ taskId: string;
11
11
  };
12
12
  export type SchedulerOutput = {
13
13
  type: "run";
14
- taskId: number;
14
+ taskId: string;
15
15
  } | {
16
16
  type: "idle";
17
17
  };
package/dist/scheduler.js CHANGED
@@ -32,9 +32,9 @@ export function* createScheduler(graph) {
32
32
  if (input?.taskId === undefined)
33
33
  continue;
34
34
  markDependencyReady(input.taskId);
35
- if (input.type === "ready")
36
- continue;
37
- completed++;
35
+ if (input.type === "complete" || input.type === "skip") {
36
+ completed++;
37
+ }
38
38
  }
39
39
  return { type: "done" };
40
40
  }
package/dist/task.d.ts CHANGED
@@ -7,10 +7,5 @@ import type { TaskConfig, Task } from "./types.js";
7
7
  * const build = task({ name: "build", commands: { build: "npm run build" }, cwd: "." })
8
8
  * const deploy = task({ name: "deploy", commands: { build: "npm run deploy" }, cwd: ".", dependencies: [build] })
9
9
  * await pipeline([build, deploy]).run()
10
- *
11
- * // alternatively, you can use the andThen method to chain tasks together:
12
- * const buildAndDeploy = task({ name: "build", commands: { build: "npm run build" }, cwd: "." })
13
- * .andThen({ name: "deploy", commands: { build: "npm run deploy" }, cwd: "." })
14
- * await buildAndDeploy.run()
15
10
  */
16
11
  export declare function task(config: TaskConfig): Task;
package/dist/task.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import { $TASK_INTERNAL } from "./constants.js";
2
2
  import { validateTasks } from "./util.js";
3
- import { pipeline } from "./pipeline.js";
4
- let taskId = 0;
5
3
  /**
6
4
  * Creates a task configuration.
7
5
  * @param config - The configuration for the task.
@@ -10,30 +8,28 @@ let taskId = 0;
10
8
  * const build = task({ name: "build", commands: { build: "npm run build" }, cwd: "." })
11
9
  * const deploy = task({ name: "deploy", commands: { build: "npm run deploy" }, cwd: ".", dependencies: [build] })
12
10
  * await pipeline([build, deploy]).run()
13
- *
14
- * // alternatively, you can use the andThen method to chain tasks together:
15
- * const buildAndDeploy = task({ name: "build", commands: { build: "npm run build" }, cwd: "." })
16
- * .andThen({ name: "deploy", commands: { build: "npm run deploy" }, cwd: "." })
17
- * await buildAndDeploy.run()
18
11
  */
19
12
  export function task(config) {
20
- validateTasks(config.dependencies);
21
- const taskInstance = {
22
- name: config.name,
13
+ const { name, commands, cwd = ".", dependencies = [], env, allowSkip, } = config;
14
+ const dependenciesClone = [...dependencies];
15
+ validateTasks(dependenciesClone);
16
+ const commandsClone = Object.fromEntries(Object.entries(commands).map(([key, command]) => {
17
+ if (typeof command === "string") {
18
+ return [key, command];
19
+ }
20
+ const { run, readyWhen, readyTimeout, teardown, env } = command;
21
+ return [key, { run, readyWhen, readyTimeout, teardown, env: { ...env } }];
22
+ }));
23
+ return {
24
+ name,
23
25
  [$TASK_INTERNAL]: {
24
- ...config,
25
- id: taskId++,
26
- dependencies: [...(config.dependencies || [])],
27
- },
28
- andThen(nextConfig) {
29
- // Create the next task with the current task as a dependency
30
- const nextTask = task({
31
- ...nextConfig,
32
- dependencies: [taskInstance],
33
- });
34
- // Return a pipeline containing both tasks
35
- return pipeline([taskInstance, nextTask]);
26
+ name,
27
+ cwd,
28
+ dependencies: dependenciesClone,
29
+ env: { ...env },
30
+ allowSkip,
31
+ id: crypto.randomUUID(),
32
+ commands: commandsClone,
36
33
  },
37
34
  };
38
- return taskInstance;
39
35
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { $TASK_INTERNAL } from "./constants.js";
2
- import { PipelineError } from "./pipeline.js";
2
+ import { PipelineError } from "./pipeline-error.js";
3
3
  /**
4
4
  * Configuration for a command to be executed as part of a task.
5
5
  */
@@ -25,6 +25,11 @@ export interface CommandConfig {
25
25
  * Optional command to run during teardown (e.g., to stop a server).
26
26
  */
27
27
  teardown?: string;
28
+ /**
29
+ * Optional environment variables to set for the process spawned by this command.
30
+ * Overrides environment variables inherited from the parent process & task config.
31
+ */
32
+ env?: Record<string, string>;
28
33
  }
29
34
  /**
30
35
  * A command can be either a simple string or a CommandConfig object.
@@ -57,8 +62,9 @@ export interface TaskConfig {
57
62
  /**
58
63
  * Working directory for the task's commands.
59
64
  * Can be absolute or relative to the current working directory.
65
+ * @default "."
60
66
  */
61
- cwd: string;
67
+ cwd?: string;
62
68
  /**
63
69
  * Optional array of tasks that must complete before this task can start.
64
70
  * Dependencies are executed in parallel when possible.
@@ -69,10 +75,17 @@ export interface TaskConfig {
69
75
  * Use this to explicitly mark tasks that are intentionally mode-specific.
70
76
  */
71
77
  allowSkip?: boolean;
78
+ /**
79
+ * Optional environment variables to set for the process spawned by this task.
80
+ * Overrides environment variables inherited from the parent process.
81
+ */
82
+ env?: Record<string, string>;
72
83
  }
73
84
  interface TaskInternal extends TaskConfig {
74
- id: number;
85
+ id: string;
86
+ cwd: string;
75
87
  dependencies: Task[];
88
+ env: Record<string, string>;
76
89
  pipeline?: Pipeline;
77
90
  }
78
91
  /**
@@ -89,13 +102,6 @@ export interface Task {
89
102
  * @internal
90
103
  */
91
104
  [$TASK_INTERNAL]: TaskInternal;
92
- /**
93
- * Creates a new pipeline that starts after this task completes.
94
- * This allows for chaining tasks together.
95
- * @param config Task configuration for the next task in the chain.
96
- * @returns A new pipeline starting with the configured task.
97
- */
98
- andThen(config: Omit<TaskConfig, "dependencies">): Pipeline;
99
105
  }
100
106
  /**
101
107
  * Configuration options for running a pipeline.
@@ -106,6 +112,11 @@ export interface PipelineRunConfig {
106
112
  * @default process.env.NODE_ENV === "production" ? "build" : "dev"
107
113
  */
108
114
  command?: string;
115
+ /**
116
+ * Optional environment variables to set for processes spawned by this pipeline.
117
+ * Overrides environment variables inherited from the parent process.
118
+ */
119
+ env?: Record<string, string>;
109
120
  /**
110
121
  * Provides a custom abort signal for the pipeline.
111
122
  * Aborting the signal will cause the pipeline to fail.
@@ -149,16 +160,6 @@ export interface PipelineRunConfig {
149
160
  * @param error The error that occurred during teardown.
150
161
  */
151
162
  onTaskTeardownError?: (taskName: string, error: Error) => void;
152
- /**
153
- * Callback invoked when the pipeline encounters an error and fails.
154
- * @param error The PipelineError that caused the pipeline to fail.
155
- */
156
- onPipelineError?: (error: PipelineError) => void;
157
- /**
158
- * Callback invoked when the pipeline completes successfully.
159
- * This is called after all tasks have completed and all teardowns have finished.
160
- */
161
- onPipelineComplete?: () => void;
162
163
  }
163
164
  /**
164
165
  * Configuration for converting a pipeline into a task.
@@ -173,6 +174,11 @@ export interface PipelineTaskConfig {
173
174
  * Optional array of tasks that must complete before this pipeline task can start.
174
175
  */
175
176
  dependencies?: Task[];
177
+ /**
178
+ * Optional environment variables to set for the process spawned by this pipeline task.
179
+ * Overrides environment variables inherited from the parent process.
180
+ */
181
+ env?: Record<string, string>;
176
182
  }
177
183
  /**
178
184
  * A pipeline manages the execution of tasks with dependency-based coordination.
@@ -184,10 +190,10 @@ export interface Pipeline {
184
190
  * Tasks with no dependencies start immediately, and tasks with dependencies
185
191
  * wait for their dependencies to complete before starting.
186
192
  * @param config Optional configuration for pipeline execution.
187
- * @returns A promise that resolves when all tasks complete successfully,
188
- * or rejects if any task fails or the pipeline is aborted.
193
+ * @returns A promise that resolves with a RunResult containing execution stats.
194
+ * The result will have `ok: false` if any task fails or the pipeline is aborted.
189
195
  */
190
- run(config?: PipelineRunConfig): Promise<void>;
196
+ run(config?: PipelineRunConfig): Promise<RunResult>;
191
197
  /**
192
198
  * Converts this pipeline into a task that can be used as a dependency
193
199
  * in another pipeline. This enables nested pipelines.
@@ -198,11 +204,11 @@ export interface Pipeline {
198
204
  }
199
205
  export interface TaskNode {
200
206
  task: Task;
201
- dependencies: Set<number>;
202
- dependents: Set<number>;
207
+ dependencies: Set<string>;
208
+ dependents: Set<string>;
203
209
  }
204
210
  export interface TaskGraph {
205
- nodes: Map<number, TaskNode>;
211
+ nodes: Map<string, TaskNode>;
206
212
  /**
207
213
  * Validates the graph for circular dependencies.
208
214
  * @throws Error if circular dependencies are detected
@@ -214,4 +220,204 @@ export interface TaskGraph {
214
220
  */
215
221
  simplify(): void;
216
222
  }
223
+ /**
224
+ * Status of a task in the pipeline.
225
+ * - "pending": Task has not started yet
226
+ * - "skipped": Task was skipped (e.g., no command for the current mode)
227
+ * - "running": Task is currently executing
228
+ * - "completed": Task completed successfully
229
+ * - "failed": Task failed during execution
230
+ * - "aborted": Task was aborted (e.g., due to pipeline cancellation)
231
+ */
232
+ export type TaskStatus = "pending" | "skipped" | "running" | "completed" | "failed" | "aborted";
233
+ /**
234
+ * Statistics for a single task in the pipeline.
235
+ */
236
+ export interface TaskStats {
237
+ /**
238
+ * Unique identifier for the task.
239
+ */
240
+ id: string;
241
+ /**
242
+ * Human-readable name of the task.
243
+ */
244
+ name: string;
245
+ /**
246
+ * Current status of the task.
247
+ */
248
+ status: TaskStatus;
249
+ /**
250
+ * Command name that was executed (e.g., "dev", "build").
251
+ * Only present if the task was executed or skipped (not if it's still pending).
252
+ */
253
+ command?: string;
254
+ /**
255
+ * Timestamp (milliseconds since epoch) when the task started execution.
256
+ * Only present if the task started running.
257
+ */
258
+ startedAt?: number;
259
+ /**
260
+ * Timestamp (milliseconds since epoch) when the task finished execution.
261
+ * Only present if the task completed, failed, was skipped, or was aborted.
262
+ */
263
+ finishedAt?: number;
264
+ /**
265
+ * Duration of task execution in milliseconds.
266
+ * Only present if the task has finished (completed, failed, skipped, or aborted).
267
+ */
268
+ durationMs?: number;
269
+ /**
270
+ * Exit code of the task's process.
271
+ * Only present if the task completed or failed.
272
+ * Typically 0 for success, non-zero for failure.
273
+ */
274
+ exitCode?: number;
275
+ /**
276
+ * Signal that terminated the task's process (e.g., "SIGTERM", "SIGKILL").
277
+ * Only present if the task was terminated by a signal.
278
+ */
279
+ signal?: string;
280
+ /**
281
+ * Error that occurred during task execution.
282
+ * Only present if the task failed or was aborted.
283
+ */
284
+ error?: Error;
285
+ /**
286
+ * Teardown command execution status.
287
+ * Only present if the task had a teardown command configured.
288
+ */
289
+ teardown?: {
290
+ /**
291
+ * Status of the teardown command execution.
292
+ * - "not-run": Teardown was registered but never executed (e.g., task failed before starting)
293
+ * - "completed": Teardown executed successfully
294
+ * - "failed": Teardown execution failed
295
+ */
296
+ status: "not-run" | "completed" | "failed";
297
+ /**
298
+ * Error that occurred during teardown execution.
299
+ * Only present if teardown status is "failed".
300
+ */
301
+ error?: Error;
302
+ };
303
+ /**
304
+ * Array of task IDs that this task depends on.
305
+ * These tasks must complete before this task can start.
306
+ */
307
+ dependencies: string[];
308
+ /**
309
+ * Array of task IDs that depend on this task.
310
+ * These tasks cannot start until this task completes.
311
+ */
312
+ dependents: string[];
313
+ }
314
+ /**
315
+ * Statistics for the entire pipeline execution.
316
+ */
317
+ export interface PipelineStats {
318
+ /**
319
+ * Command name that was executed for this pipeline run (e.g., "dev", "build").
320
+ */
321
+ command: string;
322
+ /**
323
+ * Timestamp (milliseconds since epoch) when the pipeline started.
324
+ */
325
+ startedAt: number;
326
+ /**
327
+ * Timestamp (milliseconds since epoch) when the pipeline finished.
328
+ */
329
+ finishedAt: number;
330
+ /**
331
+ * Total duration of pipeline execution in milliseconds.
332
+ */
333
+ durationMs: number;
334
+ /**
335
+ * Overall status of the pipeline.
336
+ * - "success": All tasks completed successfully
337
+ * - "failed": One or more tasks failed
338
+ * - "aborted": Pipeline was aborted (e.g., due to signal cancellation)
339
+ */
340
+ status: "success" | "failed" | "aborted";
341
+ /**
342
+ * Map of task IDs to their statistics.
343
+ * Contains statistics for all tasks in the pipeline, regardless of their status.
344
+ */
345
+ tasks: Record<string, TaskStats>;
346
+ /**
347
+ * Summary of task execution counts.
348
+ */
349
+ summary: {
350
+ /**
351
+ * Total number of tasks in the pipeline.
352
+ */
353
+ total: number;
354
+ /**
355
+ * Number of tasks that completed successfully.
356
+ */
357
+ completed: number;
358
+ /**
359
+ * Number of tasks that failed.
360
+ */
361
+ failed: number;
362
+ /**
363
+ * Number of tasks that were skipped.
364
+ */
365
+ skipped: number;
366
+ /**
367
+ * Number of tasks that were still running when the pipeline ended.
368
+ * This is useful when the pipeline was aborted - it indicates how many
369
+ * tasks were in progress and had to be terminated.
370
+ */
371
+ running: number;
372
+ };
373
+ }
374
+ /**
375
+ * Result of running a pipeline.
376
+ * The pipeline never throws - it always returns a RunResult with detailed statistics.
377
+ *
378
+ * @example
379
+ * ```ts
380
+ * const result = await pipeline(tasks).run({ command: "dev" })
381
+ *
382
+ * if (!result.ok) {
383
+ * console.error("Pipeline failed:", result.error)
384
+ * }
385
+ *
386
+ * console.log("Pipeline stats:", result.stats)
387
+ * console.log(`Completed: ${result.stats.summary.completed}`)
388
+ * console.log(`Failed: ${result.stats.summary.failed}`)
389
+ * ```
390
+ */
391
+ export type RunResult = {
392
+ /**
393
+ * Indicates the pipeline completed successfully.
394
+ * When true, `error` is always `null`.
395
+ */
396
+ ok: true;
397
+ /**
398
+ * Always `null` when `ok` is `true`.
399
+ */
400
+ error: null;
401
+ /**
402
+ * Pipeline execution statistics.
403
+ */
404
+ stats: PipelineStats;
405
+ } | {
406
+ /**
407
+ * Indicates the pipeline failed or was aborted.
408
+ * When false, `error` contains the PipelineError that caused the failure.
409
+ */
410
+ ok: false;
411
+ /**
412
+ * The error that caused the pipeline to fail.
413
+ * Check `error.code` to determine the error type (e.g., `PipelineError.TaskFailed`).
414
+ */
415
+ error: PipelineError;
416
+ /**
417
+ * Pipeline execution statistics.
418
+ * Even when the pipeline fails, stats contain information about all tasks,
419
+ * including which ones completed, failed, or were still running.
420
+ */
421
+ stats: PipelineStats;
422
+ };
217
423
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "builderman",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Simple task runner for building and developing projects.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- export {};