builderman 1.5.0 → 1.5.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/README.md CHANGED
@@ -538,7 +538,7 @@ console.log(result.stats.summary.running)
538
538
  Each task provides detailed per-task data:
539
539
 
540
540
  ```ts
541
- for (const task of Object.values(result.stats.tasks)) {
541
+ for (const task of result.stats.tasks) {
542
542
  console.log(task.name, task.status)
543
543
  console.log(task.durationMs)
544
544
 
@@ -550,6 +550,13 @@ for (const task of Object.values(result.stats.tasks)) {
550
550
  if (task.teardown) {
551
551
  console.log("Teardown:", task.teardown.status)
552
552
  }
553
+
554
+ // when using pipeline.toTask() to convert a pipeline into a task, the task will have subtasks
555
+ if (task.subtasks) {
556
+ for (const subtask of task.subtasks) {
557
+ // ...
558
+ }
559
+ }
553
560
  }
554
561
  ```
555
562
 
@@ -2,6 +2,7 @@ import * as path from "node:path";
2
2
  import * as fs from "node:fs";
3
3
  import { $TASK_INTERNAL, $PIPELINE_INTERNAL } from "./constants.js";
4
4
  import { PipelineError } from "../errors.js";
5
+ import { parseCommandLine } from "./util.js";
5
6
  /**
6
7
  * Executes a task (either a regular task or a nested pipeline).
7
8
  */
@@ -25,6 +26,10 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, context,
25
26
  // Track nested pipeline state for skip behavior
26
27
  let nestedSkippedCount = 0;
27
28
  let nestedCompletedCount = 0;
29
+ // Track unique tasks that are ready, complete, or skipped
30
+ // A task can be both ready and complete, so we use Sets to avoid double-counting
31
+ const readyOrCompleteTasks = new Set();
32
+ let didMarkOuterReady = false;
28
33
  // Get total tasks from nested pipeline
29
34
  const nestedTotalTasks = nestedPipeline[$PIPELINE_INTERNAL].graph.nodes.size;
30
35
  const commandName = config?.command ?? (process.env.NODE_ENV === "production" ? "build" : "dev");
@@ -37,7 +42,7 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, context,
37
42
  command: commandName,
38
43
  startedAt,
39
44
  });
40
- config?.onTaskBegin?.(taskName);
45
+ config?.onTaskBegin?.(taskName, taskId);
41
46
  // Create an abort controller to stop the nested pipeline if needed
42
47
  let pipelineStopped = false;
43
48
  // Merge environment variables: pipeline.env -> task.env (from pipeline.toTask config)
@@ -47,6 +52,19 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, context,
47
52
  ...pipelineEnv,
48
53
  ...taskEnv,
49
54
  };
55
+ // Helper to check if all inner tasks are ready/complete/skipped
56
+ const checkAllInnerTasksReady = () => {
57
+ if (didMarkOuterReady)
58
+ return;
59
+ // Count unique tasks: ready (via readyWhen), complete, or skipped
60
+ // A task can be both ready and complete, so we use a Set to track unique tasks
61
+ const totalFinished = readyOrCompleteTasks.size + nestedSkippedCount;
62
+ if (totalFinished === nestedTotalTasks && nestedTotalTasks > 0) {
63
+ didMarkOuterReady = true;
64
+ // Mark outer task as ready - this allows dependents to proceed
65
+ callbacks.onTaskReady(taskId);
66
+ }
67
+ };
50
68
  // Run the nested pipeline with signal propagation
51
69
  // Pass the command from parent pipeline to nested pipeline
52
70
  // If undefined, nested pipeline will use its own default (build/dev based on NODE_ENV)
@@ -57,22 +75,35 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, context,
57
75
  strict: config?.strict,
58
76
  signal: context.signal, // Pass signal to nested pipeline
59
77
  env: mergedEnv, // Pass merged env to nested pipeline
60
- onTaskBegin: (nestedTaskName) => {
78
+ onTaskBegin: (nestedTaskName, nestedTaskId) => {
61
79
  if (pipelineStopped)
62
80
  return;
63
- config?.onTaskBegin?.(`${taskName}:${nestedTaskName}`);
81
+ config?.onTaskBegin?.(`${taskName}:${nestedTaskName}`, nestedTaskId);
64
82
  },
65
- onTaskComplete: (nestedTaskName) => {
83
+ onTaskReady: (_, nestedTaskId) => {
84
+ if (pipelineStopped)
85
+ return;
86
+ // Track this task as ready (it may later become complete, but we only count it once)
87
+ readyOrCompleteTasks.add(nestedTaskId);
88
+ checkAllInnerTasksReady();
89
+ // Note: We don't call config?.onTaskReady here because the nested pipeline
90
+ // already handles that internally. We only track readiness for the outer task.
91
+ },
92
+ onTaskComplete: (nestedTaskName, nestedTaskId) => {
66
93
  if (pipelineStopped)
67
94
  return;
68
95
  nestedCompletedCount++;
69
- config?.onTaskComplete?.(`${taskName}:${nestedTaskName}`);
96
+ // Track this task as complete (if it was already ready, the Set prevents double-counting)
97
+ readyOrCompleteTasks.add(nestedTaskId);
98
+ checkAllInnerTasksReady();
99
+ config?.onTaskComplete?.(`${taskName}:${nestedTaskName}`, nestedTaskId);
70
100
  },
71
- onTaskSkipped: (nestedTaskName, mode) => {
101
+ onTaskSkipped: (nestedTaskName, nestedTaskId, mode) => {
72
102
  if (pipelineStopped)
73
103
  return;
74
104
  nestedSkippedCount++;
75
- config?.onTaskSkipped?.(`${taskName}:${nestedTaskName}`, mode);
105
+ checkAllInnerTasksReady();
106
+ config?.onTaskSkipped?.(`${taskName}:${nestedTaskName}`, nestedTaskId, mode);
76
107
  },
77
108
  })
78
109
  .then((result) => {
@@ -81,15 +112,20 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, context,
81
112
  if (!result.ok) {
82
113
  // Nested pipeline failed
83
114
  const finishedAt = Date.now();
115
+ // Capture nested pipeline task stats even on failure
116
+ const nestedTaskStats = result.stats.tasks;
84
117
  context.updateTaskStatus(taskId, {
85
118
  status: "failed",
86
119
  finishedAt,
87
120
  durationMs: finishedAt - startedAt,
88
121
  error: result.error,
122
+ subtasks: nestedTaskStats,
89
123
  });
90
124
  callbacks.onTaskFailed(taskId, result.error);
91
125
  return;
92
126
  }
127
+ // Capture nested pipeline task stats for subtasks field
128
+ const nestedTaskStats = result.stats.tasks;
93
129
  // Determine nested pipeline result based on skip behavior:
94
130
  // - If all inner tasks are skipped → outer task is skipped
95
131
  // - If some run, some skip → outer task is completed
@@ -101,8 +137,9 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, context,
101
137
  status: "skipped",
102
138
  finishedAt,
103
139
  durationMs: finishedAt - startedAt,
140
+ subtasks: nestedTaskStats,
104
141
  });
105
- config?.onTaskSkipped?.(taskName, commandName);
142
+ config?.onTaskSkipped?.(taskName, taskId, commandName);
106
143
  setImmediate(() => {
107
144
  callbacks.onTaskSkipped(taskId);
108
145
  });
@@ -114,8 +151,9 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, context,
114
151
  status: "completed",
115
152
  finishedAt,
116
153
  durationMs: finishedAt - startedAt,
154
+ subtasks: nestedTaskStats,
117
155
  });
118
- config?.onTaskComplete?.(taskName);
156
+ config?.onTaskComplete?.(taskName, taskId);
119
157
  // Mark as complete - this will update dependent tasks and allow them to start
120
158
  callbacks.onTaskComplete(taskId);
121
159
  }
@@ -150,7 +188,7 @@ function executeRegularTask(task, taskId, taskName, context, callbacks) {
150
188
  finishedAt,
151
189
  durationMs: 0,
152
190
  });
153
- config?.onTaskSkipped?.(taskName, commandName);
191
+ config?.onTaskSkipped?.(taskName, taskId, commandName);
154
192
  // Mark as skipped - this satisfies dependencies and unblocks dependents
155
193
  // Use setImmediate to ensure scheduler is at idle yield before receiving skip
156
194
  setImmediate(() => {
@@ -158,23 +196,25 @@ function executeRegularTask(task, taskId, taskName, context, callbacks) {
158
196
  });
159
197
  return;
160
198
  }
161
- let command;
199
+ let commandString;
162
200
  let readyWhen;
163
201
  let readyTimeout = Infinity;
164
202
  let completedTimeout = Infinity;
165
203
  let teardown;
166
204
  let commandEnv = {};
167
205
  if (typeof commandConfig === "string") {
168
- command = commandConfig;
206
+ commandString = commandConfig;
169
207
  }
170
208
  else {
171
- command = commandConfig.run;
209
+ commandString = commandConfig.run;
172
210
  readyWhen = commandConfig.readyWhen;
173
211
  readyTimeout = commandConfig.readyTimeout ?? Infinity;
174
212
  completedTimeout = commandConfig.completedTimeout ?? Infinity;
175
213
  teardown = commandConfig.teardown;
176
214
  commandEnv = commandConfig.env ?? {};
177
215
  }
216
+ // Parse commandSpec into cmd + args
217
+ const { cmd, args } = parseCommandLine(commandString);
178
218
  const taskCwd = path.isAbsolute(cwd) ? cwd : path.resolve(process.cwd(), cwd);
179
219
  if (!fs.existsSync(taskCwd)) {
180
220
  const finishedAt = Date.now();
@@ -205,10 +245,10 @@ function executeRegularTask(task, taskId, taskName, context, callbacks) {
205
245
  ...taskEnv,
206
246
  ...commandEnv,
207
247
  };
208
- const child = spawnFn(command, {
248
+ const child = spawnFn(cmd, args, {
209
249
  cwd: taskCwd,
210
250
  stdio: ["inherit", "pipe", "pipe"],
211
- shell: true,
251
+ shell: false,
212
252
  env: accumulatedEnv,
213
253
  });
214
254
  // Update the running task execution with the process
@@ -224,28 +264,20 @@ function executeRegularTask(task, taskId, taskName, context, callbacks) {
224
264
  });
225
265
  // Store teardown command if provided
226
266
  if (teardown) {
267
+ const teardownSpec = parseCommandLine(teardown);
227
268
  teardownManager.register(taskId, {
228
- command: teardown,
269
+ cmd: teardownSpec.cmd,
270
+ args: teardownSpec.args,
229
271
  cwd: taskCwd,
230
272
  taskName,
231
273
  });
232
274
  }
233
- config?.onTaskBegin?.(taskName);
275
+ config?.onTaskBegin?.(taskName, taskId);
234
276
  let didMarkReady = false;
235
- if (!readyWhen) {
236
- // For tasks without readyWhen, mark as ready after a microtask
237
- // This ensures the task has actually started before we update dependents
238
- // This prevents race conditions where a task fails immediately after starting
239
- setImmediate(() => {
240
- // Check if task hasn't failed before marking as ready
241
- const currentStats = context.taskStats.get(taskId);
242
- if (currentStats?.status !== "failed" && !context.isAborted()) {
243
- callbacks.onTaskReady(taskId);
244
- didMarkReady = true;
245
- }
246
- });
247
- }
248
- else if (readyTimeout !== Infinity) {
277
+ // For tasks without readyWhen, we wait for the process to exit successfully
278
+ // before allowing dependent tasks to start. This ensures dependencies are
279
+ // fully completed before dependents begin execution.
280
+ if (readyWhen && readyTimeout !== Infinity) {
249
281
  // Set up timeout for readyWhen condition using TimeoutManager
250
282
  timeoutManager.setReadyTimeout(taskId, readyTimeout, () => {
251
283
  if (!didMarkReady) {
@@ -358,7 +390,7 @@ function executeRegularTask(task, taskId, taskName, context, callbacks) {
358
390
  durationMs,
359
391
  exitCode: code ?? 0,
360
392
  });
361
- config?.onTaskComplete?.(taskName);
393
+ config?.onTaskComplete?.(taskName, taskId);
362
394
  // 🔑 Notify scheduler and drain newly runnable tasks
363
395
  callbacks.onTaskComplete(taskId);
364
396
  });
@@ -1,13 +1,14 @@
1
1
  import type { TaskGraph } from "../types.js";
2
2
  export interface TeardownCommand {
3
- command: string;
3
+ cmd: string;
4
+ args: string[];
4
5
  cwd: string;
5
6
  taskName: string;
6
7
  }
7
8
  export interface TeardownManagerConfig {
8
9
  spawn: typeof import("node:child_process").spawn;
9
- onTaskTeardown?: (taskName: string) => void;
10
- onTaskTeardownError?: (taskName: string, error: Error) => void;
10
+ onTaskTeardown?: (taskName: string, taskId: string) => void;
11
+ onTaskTeardownError?: (taskName: string, taskId: string, error: Error) => void;
11
12
  updateTaskTeardownStatus: (taskId: string, status: "not-run" | "completed" | "failed", error?: Error) => void;
12
13
  }
13
14
  export interface TeardownManager {
@@ -16,13 +16,13 @@ export function createTeardownManager(config) {
16
16
  }
17
17
  // Remove from map so it doesn't run again
18
18
  teardownCommands.delete(taskId);
19
- config.onTaskTeardown?.(teardown.taskName);
19
+ config.onTaskTeardown?.(teardown.taskName, taskId);
20
20
  return new Promise((resolve) => {
21
21
  try {
22
- const teardownProcess = config.spawn(teardown.command, {
22
+ const teardownProcess = config.spawn(teardown.cmd, teardown.args, {
23
23
  cwd: teardown.cwd,
24
24
  stdio: "inherit",
25
- shell: true,
25
+ shell: false,
26
26
  });
27
27
  let resolved = false;
28
28
  const resolveOnce = () => {
@@ -33,14 +33,14 @@ export function createTeardownManager(config) {
33
33
  };
34
34
  teardownProcess.on("error", (error) => {
35
35
  const teardownError = new Error(`[${teardown.taskName}] Teardown failed: ${error.message}`);
36
- config.onTaskTeardownError?.(teardown.taskName, teardownError);
36
+ config.onTaskTeardownError?.(teardown.taskName, taskId, teardownError);
37
37
  config.updateTaskTeardownStatus(taskId, "failed", teardownError);
38
38
  resolveOnce();
39
39
  });
40
40
  teardownProcess.on("exit", (code) => {
41
41
  if (code !== 0) {
42
42
  const teardownError = new Error(`[${teardown.taskName}] Teardown failed with exit code ${code ?? 1}`);
43
- config.onTaskTeardownError?.(teardown.taskName, teardownError);
43
+ config.onTaskTeardownError?.(teardown.taskName, taskId, teardownError);
44
44
  config.updateTaskTeardownStatus(taskId, "failed", teardownError);
45
45
  }
46
46
  else {
@@ -51,7 +51,7 @@ export function createTeardownManager(config) {
51
51
  }
52
52
  catch (error) {
53
53
  const teardownError = new Error(`[${teardown.taskName}] Teardown failed to start: ${error.message}`);
54
- config.onTaskTeardownError?.(teardown.taskName, teardownError);
54
+ config.onTaskTeardownError?.(teardown.taskName, taskId, teardownError);
55
55
  config.updateTaskTeardownStatus(taskId, "failed", teardownError);
56
56
  resolve();
57
57
  }
@@ -1,2 +1,16 @@
1
1
  import type { Task } from "../types.js";
2
2
  export declare function validateTasks(tasks?: Task[]): void;
3
+ /**
4
+ * Very small, cross-platform command-line parser for our default spawn wrapper.
5
+ * - Splits on whitespace outside of single/double quotes
6
+ * - Strips the surrounding quotes
7
+ * - Does NOT implement full shell semantics (no pipes, redirects, etc.)
8
+ *
9
+ * This is sufficient for commands like:
10
+ * - `"node path/to/script.js"`
11
+ * - `"\"C:\\Program Files\\nodejs\\node.exe\" \"C:\\path with spaces\\script.js\""`
12
+ */
13
+ export declare function parseCommandLine(command: string): {
14
+ cmd: string;
15
+ args: string[];
16
+ };
@@ -4,3 +4,53 @@ export function validateTasks(tasks) {
4
4
  throw new Error("Invalid dependency: must be a task or a pipeline converted to a task");
5
5
  }
6
6
  }
7
+ /**
8
+ * Very small, cross-platform command-line parser for our default spawn wrapper.
9
+ * - Splits on whitespace outside of single/double quotes
10
+ * - Strips the surrounding quotes
11
+ * - Does NOT implement full shell semantics (no pipes, redirects, etc.)
12
+ *
13
+ * This is sufficient for commands like:
14
+ * - `"node path/to/script.js"`
15
+ * - `"\"C:\\Program Files\\nodejs\\node.exe\" \"C:\\path with spaces\\script.js\""`
16
+ */
17
+ export function parseCommandLine(command) {
18
+ const tokens = [];
19
+ let current = "";
20
+ let inQuotes = false;
21
+ let quoteChar = null;
22
+ for (let i = 0; i < command.length; i++) {
23
+ const ch = command[i];
24
+ if (ch === '"' || ch === "'") {
25
+ if (!inQuotes) {
26
+ inQuotes = true;
27
+ quoteChar = ch;
28
+ continue;
29
+ }
30
+ if (quoteChar === ch) {
31
+ inQuotes = false;
32
+ quoteChar = null;
33
+ continue;
34
+ }
35
+ // Different quote type inside current quotes – treat as literal
36
+ current += ch;
37
+ continue;
38
+ }
39
+ if (!inQuotes && /\s/.test(ch)) {
40
+ if (current.length > 0) {
41
+ tokens.push(current);
42
+ current = "";
43
+ }
44
+ continue;
45
+ }
46
+ current += ch;
47
+ }
48
+ if (current.length > 0) {
49
+ tokens.push(current);
50
+ }
51
+ if (tokens.length === 0) {
52
+ throw new Error(`Invalid command: "${command}"`);
53
+ }
54
+ const [cmd, ...args] = tokens;
55
+ return { cmd, args };
56
+ }
package/dist/pipeline.js CHANGED
@@ -144,6 +144,10 @@ export function pipeline(tasks) {
144
144
  // Task is ready (via readyWhen) - update dependent tasks immediately
145
145
  // Only process ready tasks if pipeline hasn't failed
146
146
  if (!isPipelineFailed()) {
147
+ const taskName = taskStats.get(taskId)?.name;
148
+ if (taskName) {
149
+ config?.onTaskReady?.(taskName, taskId);
150
+ }
147
151
  queueManager.markRunningTaskReady(taskId);
148
152
  // Process newly ready tasks immediately
149
153
  processReadyTasks();
@@ -171,10 +175,16 @@ export function pipeline(tasks) {
171
175
  const isPipelineFailed = () => {
172
176
  return failed || queueManager.hasFailed();
173
177
  };
174
- // Helper to safely kill a process (ignores errors if process is already dead)
178
+ // Helper to safely kill a process (ignores errors if process is already dead).
179
+ // With shell:false and direct child processes, a single SIGTERM is usually enough;
180
+ // we avoid force-killing from the outside so the child has a chance to run its
181
+ // own signal handlers and exit cleanly.
175
182
  const safeKillProcess = (process) => {
183
+ if (!process)
184
+ return;
176
185
  try {
177
- process?.kill("SIGTERM");
186
+ // Try a graceful SIGTERM and let the child handle it.
187
+ process.kill("SIGTERM");
178
188
  }
179
189
  catch {
180
190
  // Process might already be dead, ignore
@@ -236,7 +246,7 @@ export function pipeline(tasks) {
236
246
  const signalHandler = createSignalHandler({
237
247
  abortSignal: signal,
238
248
  onAborted: () => failPipeline(new PipelineError("Aborted", PipelineError.Aborted)),
239
- onProcessTerminated: (message) => new PipelineError(message, PipelineError.ProcessTerminated),
249
+ onProcessTerminated: (message) => failPipeline(new PipelineError(message, PipelineError.ProcessTerminated)),
240
250
  });
241
251
  // 🚀 Start processing ready tasks
242
252
  processReadyTasks();
@@ -268,11 +278,8 @@ export function pipeline(tasks) {
268
278
  function buildResult(error, startedAt, command, taskStats, status) {
269
279
  const finishedAt = Date.now();
270
280
  const durationMs = finishedAt - startedAt;
271
- // Convert task stats map to record
272
- const tasks = {};
273
- for (const [taskId, stats] of taskStats) {
274
- tasks[taskId] = stats;
275
- }
281
+ // Convert task stats map to array
282
+ const tasks = Array.from(taskStats.values());
276
283
  // Calculate summary
277
284
  let completed = 0;
278
285
  let failed = 0;
package/dist/types.d.ts CHANGED
@@ -145,31 +145,42 @@ export interface PipelineRunConfig {
145
145
  /**
146
146
  * Callback invoked when a task begins execution.
147
147
  * @param taskName The name of the task that started.
148
+ * @param taskId The id of the task that started.
148
149
  */
149
- onTaskBegin?: (taskName: string) => void;
150
+ onTaskBegin?: (taskName: string, taskId: string) => void;
151
+ /**
152
+ * Callback invoked when a task becomes ready (via readyWhen condition).
153
+ * This is called when a task with a `readyWhen` function satisfies its condition.
154
+ * @param taskName The name of the task that became ready.
155
+ * @param taskId The id of the task that became ready.
156
+ */
157
+ onTaskReady?: (taskName: string, taskId: string) => void;
150
158
  /**
151
159
  * Callback invoked when a task completes successfully.
152
160
  * @param taskName The name of the task that completed.
161
+ * @param taskId The id of the task that completed.
153
162
  */
154
- onTaskComplete?: (taskName: string) => void;
163
+ onTaskComplete?: (taskName: string, taskId: string) => void;
155
164
  /**
156
165
  * Callback invoked when a task is skipped (e.g., when a command doesn't exist for the current mode).
157
166
  * @param taskName The name of the task that was skipped.
167
+ * @param taskId The id of the task that was skipped.
158
168
  * @param mode The command mode that was requested (e.g., "dev", "build").
159
169
  */
160
- onTaskSkipped?: (taskName: string, mode: string) => void;
170
+ onTaskSkipped?: (taskName: string, taskId: string, mode: string) => void;
161
171
  /**
162
172
  * Callback invoked when a task's teardown command begins execution.
163
173
  * @param taskName The name of the task whose teardown is running.
174
+ * @param taskId The id of the task whose teardown is running.
164
175
  */
165
- onTaskTeardown?: (taskName: string) => void;
176
+ onTaskTeardown?: (taskName: string, taskId: string) => void;
166
177
  /**
167
178
  * Callback invoked when a task's teardown command fails.
168
179
  * Note: Teardown failures do not cause the pipeline to fail.
169
180
  * @param taskName The name of the task whose teardown failed.
170
181
  * @param error The error that occurred during teardown.
171
182
  */
172
- onTaskTeardownError?: (taskName: string, error: Error) => void;
183
+ onTaskTeardownError?: (taskName: string, taskId: string, error: Error) => void;
173
184
  }
174
185
  /**
175
186
  * Configuration for converting a pipeline into a task.
@@ -329,6 +340,12 @@ export interface TaskStats {
329
340
  * These tasks cannot start until this task completes.
330
341
  */
331
342
  dependents: string[];
343
+ /**
344
+ * Statistics for nested tasks when this task represents a pipeline
345
+ * (created via pipeline.toTask()). Only present for pipeline-tasks.
346
+ * Contains statistics for all tasks within the nested pipeline.
347
+ */
348
+ subtasks?: TaskStats[];
332
349
  }
333
350
  /**
334
351
  * Statistics for the entire pipeline execution.
@@ -358,10 +375,10 @@ export interface PipelineStats {
358
375
  */
359
376
  status: "success" | "failed" | "aborted";
360
377
  /**
361
- * Map of task IDs to their statistics.
378
+ * Array of task statistics.
362
379
  * Contains statistics for all tasks in the pipeline, regardless of their status.
363
380
  */
364
- tasks: Record<string, TaskStats>;
381
+ tasks: TaskStats[];
365
382
  /**
366
383
  * Summary of task execution counts.
367
384
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "builderman",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Simple task runner for building and developing projects.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",