builderman 1.0.3 → 1.0.4

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,187 @@
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 the shell from environment or default to cmd.exe
72
+ // In Git Bash, use bash instead
73
+ let shell = true;
74
+ if (process.platform === "win32") {
75
+ // Check if we're in Git Bash (SHELL env var is set)
76
+ if (process.env.SHELL && process.env.SHELL.includes("bash")) {
77
+ shell = process.env.SHELL;
78
+ }
79
+ else {
80
+ // Use ComSpec (cmd.exe) or fallback
81
+ shell = process.env.ComSpec || "cmd.exe";
82
+ }
83
+ }
84
+ const child = spawn(command, {
85
+ cwd,
86
+ stdio: ["inherit", "pipe", "pipe"],
87
+ shell,
88
+ env,
89
+ });
90
+ // Handle spawn errors
91
+ child.on("error", (error) => {
92
+ console.error(`[${task.name}] Failed to start:`, error.message);
93
+ runningTasks.delete(task.name);
94
+ completedTasks.add(task.name);
95
+ markComplete();
96
+ process.exitCode = 1;
97
+ eventEmitter.emit("taskCompleted", task.name);
98
+ });
99
+ runningTasks.set(task.name, child);
100
+ // If task doesn't have readyOn, mark as ready immediately
101
+ if (!readyOn) {
102
+ readyTasks.add(task.name);
103
+ markReady();
104
+ eventEmitter.emit("taskReady", task.name);
105
+ }
106
+ // Monitor stdout for readiness
107
+ let stdoutBuffer = "";
108
+ let allOutput = "";
109
+ child.stdout?.on("data", (data) => {
110
+ const chunk = data.toString();
111
+ allOutput += chunk;
112
+ stdoutBuffer += chunk;
113
+ const lines = stdoutBuffer.split("\n");
114
+ stdoutBuffer = lines.pop() || "";
115
+ for (const line of lines) {
116
+ // Check if task is ready based on readyOn callback
117
+ if (readyOn && !readyTasks.has(task.name)) {
118
+ if (readyOn(allOutput)) {
119
+ readyTasks.add(task.name);
120
+ markReady();
121
+ eventEmitter.emit("taskReady", task.name);
122
+ }
123
+ }
124
+ // Forward stdout to parent
125
+ process.stdout.write(line + "\n");
126
+ }
127
+ });
128
+ // Forward any remaining buffer on end
129
+ child.stdout?.on("end", () => {
130
+ if (stdoutBuffer) {
131
+ process.stdout.write(stdoutBuffer);
132
+ }
133
+ });
134
+ // Forward stderr
135
+ child.stderr?.on("data", (data) => {
136
+ process.stderr.write(data);
137
+ });
138
+ // Handle task completion
139
+ child.on("exit", (code) => {
140
+ runningTasks.delete(task.name);
141
+ completedTasks.add(task.name);
142
+ markComplete();
143
+ if (code !== 0) {
144
+ process.exitCode = code || 1;
145
+ }
146
+ eventEmitter.emit("taskCompleted", task.name);
147
+ });
148
+ };
149
+ // Function to try starting tasks when dependencies are ready
150
+ const tryStartTasks = async () => {
151
+ for (const task of tasks) {
152
+ if (await canStart(task)) {
153
+ await startTask(task);
154
+ }
155
+ }
156
+ };
157
+ // Listen for task readiness and completion to start dependent tasks
158
+ eventEmitter.on("taskReady", tryStartTasks);
159
+ eventEmitter.on("taskCompleted", tryStartTasks);
160
+ // Start tasks that don't have dependencies
161
+ await tryStartTasks();
162
+ // Wait for all tasks to complete
163
+ return new Promise((resolve, reject) => {
164
+ const checkCompletion = () => {
165
+ if (runningTasks.size === 0) {
166
+ resolve();
167
+ }
168
+ };
169
+ eventEmitter.on("taskCompleted", checkCompletion);
170
+ checkCompletion();
171
+ // Handle process termination
172
+ process.on("SIGINT", () => {
173
+ for (const child of runningTasks.values()) {
174
+ child.kill("SIGINT");
175
+ }
176
+ reject(new Error("Process interrupted"));
177
+ });
178
+ process.on("SIGTERM", () => {
179
+ for (const child of runningTasks.values()) {
180
+ child.kill("SIGTERM");
181
+ }
182
+ reject(new Error("Process terminated"));
183
+ });
184
+ });
185
+ },
186
+ };
187
+ }
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.4",
4
4
  "description": "Simple task runner for building and developing projects.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",