builderman 1.0.3 → 1.0.5

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.
@@ -0,0 +1 @@
1
+ export declare const $TASK_INTERNAL: unique symbol;
@@ -0,0 +1 @@
1
+ export const $TASK_INTERNAL = Symbol("task-internal");
package/dist/index.d.ts CHANGED
@@ -1,26 +1,3 @@
1
- export interface Commands {
2
- dev: string;
3
- build: string;
4
- }
5
- export interface TaskConfig {
6
- name: string;
7
- commands: Commands;
8
- cwd: string;
9
- readyOn?: (output: string) => boolean;
10
- dependsOn?: Dependency[];
11
- }
12
- export type Dependency = Promise<void> | (() => Promise<void>);
13
- export interface Task extends TaskConfig {
14
- readyOrComplete(): Promise<void>;
15
- }
16
- export interface Pipeline {
17
- run(): Promise<void>;
18
- }
19
- /**
20
- * Creates a task configuration.
21
- */
22
- export declare function task(config: TaskConfig): Task;
23
- /**
24
- * Creates a pipeline that manages task execution with dependency-based coordination.
25
- */
26
- export declare function pipeline(tasks: Task[]): Pipeline;
1
+ export * from "./task.js";
2
+ export * from "./pipeline.js";
3
+ export type { Task, Pipeline, TaskConfig } from "./types.js";
package/dist/index.js CHANGED
@@ -1,205 +1,2 @@
1
- import { spawn } from "node:child_process";
2
- import { EventEmitter } from "node:events";
3
- import * as path from "node:path";
4
- /**
5
- * Creates a task configuration.
6
- */
7
- export function task(config) {
8
- let resolveReady = null;
9
- const readyPromise = new Promise((resolve) => {
10
- resolveReady = resolve;
11
- });
12
- let isReady = false;
13
- let isComplete = false;
14
- const task = {
15
- ...config,
16
- dependsOn: config.dependsOn || [],
17
- readyOrComplete() {
18
- return readyPromise;
19
- },
20
- };
21
- task._markReady = () => {
22
- if (!isReady && resolveReady) {
23
- isReady = true;
24
- resolveReady();
25
- }
26
- };
27
- task._markComplete = () => {
28
- if (!isComplete) {
29
- isComplete = true;
30
- if (!isReady && resolveReady) {
31
- isReady = true;
32
- resolveReady();
33
- }
34
- }
35
- };
36
- task._isReady = () => isReady;
37
- task._isComplete = () => isComplete;
38
- return task;
39
- }
40
- /**
41
- * Creates a pipeline that manages task execution with dependency-based coordination.
42
- */
43
- export function pipeline(tasks) {
44
- return {
45
- async run() {
46
- const eventEmitter = new EventEmitter();
47
- const runningTasks = new Map();
48
- const completedTasks = new Set();
49
- const readyTasks = new Set();
50
- // Determine which command to use based on NODE_ENV
51
- const isProduction = process.env.NODE_ENV === "production";
52
- const getCommand = (task) => {
53
- return isProduction ? task.commands.build : task.commands.dev;
54
- };
55
- // Function to check if a task's dependencies are satisfied
56
- const canStart = async (task) => {
57
- if (runningTasks.has(task.name) || completedTasks.has(task.name)) {
58
- return false;
59
- }
60
- if (!task.dependsOn || task.dependsOn.length === 0) {
61
- return true;
62
- }
63
- // Wait for all dependencies
64
- for (const dep of task.dependsOn) {
65
- if (typeof dep === "function") {
66
- await dep();
67
- }
68
- else {
69
- await dep;
70
- }
71
- }
72
- return true;
73
- };
74
- // Function to start a task
75
- const startTask = async (task) => {
76
- if (runningTasks.has(task.name)) {
77
- return;
78
- }
79
- const command = getCommand(task);
80
- // Ensure node_modules/.bin is in PATH for local dependencies
81
- const taskCwd = path.isAbsolute(task.cwd)
82
- ? task.cwd
83
- : path.resolve(process.cwd(), task.cwd);
84
- const localBinPath = path.join(taskCwd, "node_modules", ".bin");
85
- // Build PATH with local node_modules/.bin first
86
- const existingPath = process.env.PATH || process.env.Path || "";
87
- const pathSeparator = process.platform === "win32" ? ";" : ":";
88
- const binPaths = [localBinPath];
89
- const rootBinPath = path.join(process.cwd(), "node_modules", ".bin");
90
- if (rootBinPath !== localBinPath) {
91
- binPaths.push(rootBinPath);
92
- }
93
- if (existingPath) {
94
- binPaths.push(existingPath);
95
- }
96
- const newPath = binPaths.join(pathSeparator);
97
- const env = {
98
- ...process.env,
99
- PATH: newPath,
100
- Path: newPath,
101
- };
102
- const child = spawn(command, {
103
- cwd: task.cwd,
104
- stdio: ["inherit", "pipe", "pipe"],
105
- shell: true,
106
- env,
107
- });
108
- // Handle spawn errors
109
- child.on("error", (error) => {
110
- console.error(`[${task.name}] Failed to start:`, error.message);
111
- runningTasks.delete(task.name);
112
- completedTasks.add(task.name);
113
- task._markComplete();
114
- process.exitCode = 1;
115
- eventEmitter.emit("taskCompleted", task.name);
116
- });
117
- runningTasks.set(task.name, child);
118
- // If task doesn't have readyOn, mark as ready immediately
119
- if (!task.readyOn) {
120
- readyTasks.add(task.name);
121
- task._markReady();
122
- eventEmitter.emit("taskReady", task.name);
123
- }
124
- // Monitor stdout for readiness
125
- let stdoutBuffer = "";
126
- let allOutput = "";
127
- child.stdout?.on("data", (data) => {
128
- const chunk = data.toString();
129
- allOutput += chunk;
130
- stdoutBuffer += chunk;
131
- const lines = stdoutBuffer.split("\n");
132
- stdoutBuffer = lines.pop() || "";
133
- for (const line of lines) {
134
- // Check if task is ready based on readyOn callback
135
- if (task.readyOn && !readyTasks.has(task.name)) {
136
- if (task.readyOn(allOutput)) {
137
- readyTasks.add(task.name);
138
- task._markReady();
139
- eventEmitter.emit("taskReady", task.name);
140
- }
141
- }
142
- // Forward stdout to parent
143
- process.stdout.write(line + "\n");
144
- }
145
- });
146
- // Forward any remaining buffer on end
147
- child.stdout?.on("end", () => {
148
- if (stdoutBuffer) {
149
- process.stdout.write(stdoutBuffer);
150
- }
151
- });
152
- // Forward stderr
153
- child.stderr?.on("data", (data) => {
154
- process.stderr.write(data);
155
- });
156
- // Handle task completion
157
- child.on("exit", (code) => {
158
- runningTasks.delete(task.name);
159
- completedTasks.add(task.name);
160
- task._markComplete();
161
- if (code !== 0) {
162
- process.exitCode = code || 1;
163
- }
164
- eventEmitter.emit("taskCompleted", task.name);
165
- });
166
- };
167
- // Function to try starting tasks when dependencies are ready
168
- const tryStartTasks = async () => {
169
- for (const task of tasks) {
170
- if (await canStart(task)) {
171
- await startTask(task);
172
- }
173
- }
174
- };
175
- // Listen for task readiness and completion to start dependent tasks
176
- eventEmitter.on("taskReady", tryStartTasks);
177
- eventEmitter.on("taskCompleted", tryStartTasks);
178
- // Start tasks that don't have dependencies
179
- await tryStartTasks();
180
- // Wait for all tasks to complete
181
- return new Promise((resolve, reject) => {
182
- const checkCompletion = () => {
183
- if (runningTasks.size === 0) {
184
- resolve();
185
- }
186
- };
187
- eventEmitter.on("taskCompleted", checkCompletion);
188
- checkCompletion();
189
- // Handle process termination
190
- process.on("SIGINT", () => {
191
- for (const child of runningTasks.values()) {
192
- child.kill("SIGINT");
193
- }
194
- reject(new Error("Process interrupted"));
195
- });
196
- process.on("SIGTERM", () => {
197
- for (const child of runningTasks.values()) {
198
- child.kill("SIGTERM");
199
- }
200
- reject(new Error("Process terminated"));
201
- });
202
- });
203
- },
204
- };
205
- }
1
+ export * from "./task.js";
2
+ export * from "./pipeline.js";
@@ -0,0 +1,5 @@
1
+ import type { Pipeline, Task } from "./types.js";
2
+ /**
3
+ * Creates a pipeline that manages task execution with dependency-based coordination.
4
+ */
5
+ export declare function pipeline(tasks: Task[]): Pipeline;
@@ -0,0 +1,184 @@
1
+ import { spawn } from "node:child_process";
2
+ import { EventEmitter } from "node:events";
3
+ import * as path from "node:path";
4
+ import { $TASK_INTERNAL } from "./constants.js";
5
+ /**
6
+ * Creates a pipeline that manages task execution with dependency-based coordination.
7
+ */
8
+ export function pipeline(tasks) {
9
+ return {
10
+ async run() {
11
+ const eventEmitter = new EventEmitter();
12
+ const runningTasks = new Map();
13
+ const completedTasks = new Set();
14
+ const readyTasks = new Set();
15
+ // Determine which command to use based on NODE_ENV
16
+ const isProduction = process.env.NODE_ENV === "production";
17
+ const getCommand = (task) => {
18
+ return isProduction
19
+ ? task[$TASK_INTERNAL].commands.build
20
+ : task[$TASK_INTERNAL].commands.dev;
21
+ };
22
+ // Function to check if a task's dependencies are satisfied
23
+ const canStart = async (task) => {
24
+ if (runningTasks.has(task.name) || completedTasks.has(task.name)) {
25
+ return false;
26
+ }
27
+ const { dependsOn: dependencies } = task[$TASK_INTERNAL];
28
+ if (!dependencies || dependencies.length === 0) {
29
+ return true;
30
+ }
31
+ // Wait for all dependencies
32
+ for (const dep of dependencies) {
33
+ if (typeof dep === "function") {
34
+ await dep();
35
+ }
36
+ else {
37
+ await dep;
38
+ }
39
+ }
40
+ return true;
41
+ };
42
+ // Function to start a task
43
+ const startTask = async (task) => {
44
+ if (runningTasks.has(task.name)) {
45
+ return;
46
+ }
47
+ const command = getCommand(task);
48
+ const { cwd, readyOn, markComplete, markReady } = task[$TASK_INTERNAL];
49
+ // Ensure node_modules/.bin is in PATH for local dependencies
50
+ const taskCwd = path.isAbsolute(cwd)
51
+ ? cwd
52
+ : path.resolve(process.cwd(), cwd);
53
+ const localBinPath = path.join(taskCwd, "node_modules", ".bin");
54
+ // Build PATH with local node_modules/.bin first
55
+ const existingPath = process.env.PATH || process.env.Path || "";
56
+ const pathSeparator = process.platform === "win32" ? ";" : ":";
57
+ const binPaths = [localBinPath];
58
+ const rootBinPath = path.join(process.cwd(), "node_modules", ".bin");
59
+ if (rootBinPath !== localBinPath) {
60
+ binPaths.push(rootBinPath);
61
+ }
62
+ if (existingPath) {
63
+ binPaths.push(existingPath);
64
+ }
65
+ const newPath = binPaths.join(pathSeparator);
66
+ const env = {
67
+ ...process.env,
68
+ PATH: newPath,
69
+ Path: newPath,
70
+ };
71
+ // On Windows, use cmd.exe explicitly to avoid path resolution issues
72
+ // Using shell: true can cause issues with Git Bash paths
73
+ const spawnOptions = {
74
+ cwd,
75
+ stdio: ["inherit", "pipe", "pipe"],
76
+ env,
77
+ };
78
+ if (process.platform === "win32") {
79
+ // Use cmd.exe explicitly on Windows
80
+ spawnOptions.shell = process.env.ComSpec || "cmd.exe";
81
+ }
82
+ else {
83
+ // On Unix-like systems, use shell: true
84
+ spawnOptions.shell = true;
85
+ }
86
+ const child = spawn(command, spawnOptions);
87
+ // Handle spawn errors
88
+ child.on("error", (error) => {
89
+ console.error(`[${task.name}] Failed to start:`, error.message);
90
+ runningTasks.delete(task.name);
91
+ completedTasks.add(task.name);
92
+ markComplete();
93
+ process.exitCode = 1;
94
+ eventEmitter.emit("taskCompleted", task.name);
95
+ });
96
+ runningTasks.set(task.name, child);
97
+ // If task doesn't have readyOn, mark as ready immediately
98
+ if (!readyOn) {
99
+ readyTasks.add(task.name);
100
+ markReady();
101
+ eventEmitter.emit("taskReady", task.name);
102
+ }
103
+ // Monitor stdout for readiness
104
+ let stdoutBuffer = "";
105
+ let allOutput = "";
106
+ child.stdout?.on("data", (data) => {
107
+ const chunk = data.toString();
108
+ allOutput += chunk;
109
+ stdoutBuffer += chunk;
110
+ const lines = stdoutBuffer.split("\n");
111
+ stdoutBuffer = lines.pop() || "";
112
+ for (const line of lines) {
113
+ // Check if task is ready based on readyOn callback
114
+ if (readyOn && !readyTasks.has(task.name)) {
115
+ if (readyOn(allOutput)) {
116
+ readyTasks.add(task.name);
117
+ markReady();
118
+ eventEmitter.emit("taskReady", task.name);
119
+ }
120
+ }
121
+ // Forward stdout to parent
122
+ process.stdout.write(line + "\n");
123
+ }
124
+ });
125
+ // Forward any remaining buffer on end
126
+ child.stdout?.on("end", () => {
127
+ if (stdoutBuffer) {
128
+ process.stdout.write(stdoutBuffer);
129
+ }
130
+ });
131
+ // Forward stderr
132
+ child.stderr?.on("data", (data) => {
133
+ process.stderr.write(data);
134
+ });
135
+ // Handle task completion
136
+ child.on("exit", (code) => {
137
+ runningTasks.delete(task.name);
138
+ completedTasks.add(task.name);
139
+ markComplete();
140
+ if (code !== 0) {
141
+ process.exitCode = code || 1;
142
+ }
143
+ eventEmitter.emit("taskCompleted", task.name);
144
+ });
145
+ };
146
+ // Function to try starting tasks when dependencies are ready
147
+ const tryStartTasks = async () => {
148
+ for (const task of tasks) {
149
+ if (await canStart(task)) {
150
+ await startTask(task);
151
+ }
152
+ }
153
+ };
154
+ // Listen for task readiness and completion to start dependent tasks
155
+ eventEmitter.on("taskReady", tryStartTasks);
156
+ eventEmitter.on("taskCompleted", tryStartTasks);
157
+ // Start tasks that don't have dependencies
158
+ await tryStartTasks();
159
+ // Wait for all tasks to complete
160
+ return new Promise((resolve, reject) => {
161
+ const checkCompletion = () => {
162
+ if (runningTasks.size === 0) {
163
+ resolve();
164
+ }
165
+ };
166
+ eventEmitter.on("taskCompleted", checkCompletion);
167
+ checkCompletion();
168
+ // Handle process termination
169
+ process.on("SIGINT", () => {
170
+ for (const child of runningTasks.values()) {
171
+ child.kill("SIGINT");
172
+ }
173
+ reject(new Error("Process interrupted"));
174
+ });
175
+ process.on("SIGTERM", () => {
176
+ for (const child of runningTasks.values()) {
177
+ child.kill("SIGTERM");
178
+ }
179
+ reject(new Error("Process terminated"));
180
+ });
181
+ });
182
+ },
183
+ };
184
+ }
package/dist/task.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { TaskConfig, Task } from "./types.js";
2
+ /**
3
+ * Creates a task configuration.
4
+ */
5
+ export declare function task(config: TaskConfig): Task;
package/dist/task.js ADDED
@@ -0,0 +1,39 @@
1
+ import { $TASK_INTERNAL } from "./constants.js";
2
+ /**
3
+ * Creates a task configuration.
4
+ */
5
+ export function task(config) {
6
+ let resolveReady = null;
7
+ const readyPromise = new Promise((resolve) => {
8
+ resolveReady = resolve;
9
+ });
10
+ let isReady = false;
11
+ let isComplete = false;
12
+ return {
13
+ name: config.name,
14
+ readyOrComplete() {
15
+ return readyPromise;
16
+ },
17
+ [$TASK_INTERNAL]: {
18
+ ...config,
19
+ dependsOn: config.dependsOn || [],
20
+ isReady: () => isReady,
21
+ isComplete: () => isComplete,
22
+ markReady: () => {
23
+ if (!isReady && resolveReady) {
24
+ isReady = true;
25
+ resolveReady();
26
+ }
27
+ },
28
+ markComplete: () => {
29
+ if (!isComplete) {
30
+ isComplete = true;
31
+ if (!isReady && resolveReady) {
32
+ isReady = true;
33
+ resolveReady();
34
+ }
35
+ }
36
+ },
37
+ },
38
+ };
39
+ }
@@ -0,0 +1,29 @@
1
+ import type { $TASK_INTERNAL } from "./constants.js";
2
+ export interface Commands {
3
+ dev: string;
4
+ build: string;
5
+ }
6
+ export interface TaskConfig {
7
+ name: string;
8
+ commands: Commands;
9
+ cwd: string;
10
+ readyOn?: (output: string) => boolean;
11
+ dependsOn?: Dependency[];
12
+ }
13
+ export type Dependency = Promise<void> | (() => Promise<void>);
14
+ interface TaskInternal extends TaskConfig {
15
+ dependsOn: Dependency[];
16
+ isReady: () => boolean;
17
+ isComplete: () => boolean;
18
+ markReady: () => void;
19
+ markComplete: () => void;
20
+ }
21
+ export interface Task {
22
+ name: string;
23
+ readyOrComplete(): Promise<void>;
24
+ [$TASK_INTERNAL]: TaskInternal;
25
+ }
26
+ export interface Pipeline {
27
+ run(): Promise<void>;
28
+ }
29
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "builderman",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Simple task runner for building and developing projects.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",