builderman 1.0.2 → 1.0.3

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/dist/index.d.ts CHANGED
@@ -1,25 +1,26 @@
1
+ export interface Commands {
2
+ dev: string;
3
+ build: string;
4
+ }
1
5
  export interface TaskConfig {
2
6
  name: string;
3
- command: string;
7
+ commands: Commands;
4
8
  cwd: string;
5
- requiresEvents?: string[];
9
+ readyOn?: (output: string) => boolean;
10
+ dependsOn?: Dependency[];
6
11
  }
12
+ export type Dependency = Promise<void> | (() => Promise<void>);
7
13
  export interface Task extends TaskConfig {
8
- requiresEvents: string[];
14
+ readyOrComplete(): Promise<void>;
9
15
  }
10
16
  export interface Pipeline {
11
17
  run(): Promise<void>;
12
18
  }
13
- /**
14
- * Emits an event that can be received by the parent process.
15
- * Events are written to stdout in a parseable format.
16
- */
17
- export declare function emit(event: string): void;
18
19
  /**
19
20
  * Creates a task configuration.
20
21
  */
21
22
  export declare function task(config: TaskConfig): Task;
22
23
  /**
23
- * Creates a pipeline that manages task execution with event-based dependencies.
24
+ * Creates a pipeline that manages task execution with dependency-based coordination.
24
25
  */
25
26
  export declare function pipeline(tasks: Task[]): Pipeline;
package/dist/index.js CHANGED
@@ -1,71 +1,95 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { EventEmitter } from "node:events";
3
3
  import * as path from "node:path";
4
- const EVENT_PREFIX = "__BUILDERMAN_EVENT__:";
5
- /**
6
- * Emits an event that can be received by the parent process.
7
- * Events are written to stdout in a parseable format.
8
- */
9
- export function emit(event) {
10
- const eventData = JSON.stringify({ event });
11
- console.log(`${EVENT_PREFIX}${eventData}`);
12
- }
13
4
  /**
14
5
  * Creates a task configuration.
15
6
  */
16
7
  export function task(config) {
17
- return {
18
- name: config.name,
19
- command: config.command,
20
- cwd: config.cwd,
21
- requiresEvents: config.requiresEvents || [],
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
+ }
22
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;
23
39
  }
24
40
  /**
25
- * Creates a pipeline that manages task execution with event-based dependencies.
41
+ * Creates a pipeline that manages task execution with dependency-based coordination.
26
42
  */
27
43
  export function pipeline(tasks) {
28
44
  return {
29
45
  async run() {
30
46
  const eventEmitter = new EventEmitter();
31
- const emittedEvents = new Set();
32
47
  const runningTasks = new Map();
33
48
  const completedTasks = new Set();
34
- const taskConfigs = new Map();
35
- // Index tasks by name
36
- for (const task of tasks) {
37
- taskConfigs.set(task.name, task);
38
- }
39
- // Function to check if a task can start
40
- const canStart = (task) => {
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) => {
41
57
  if (runningTasks.has(task.name) || completedTasks.has(task.name)) {
42
58
  return false;
43
59
  }
44
- return task.requiresEvents.every((event) => emittedEvents.has(event));
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;
45
73
  };
46
74
  // Function to start a task
47
- const startTask = (task) => {
75
+ const startTask = async (task) => {
48
76
  if (runningTasks.has(task.name)) {
49
77
  return;
50
78
  }
51
- console.error(`[builderman] Starting task: ${task.name}`);
79
+ const command = getCommand(task);
52
80
  // Ensure node_modules/.bin is in PATH for local dependencies
53
- // Resolve cwd relative to current working directory
54
81
  const taskCwd = path.isAbsolute(task.cwd)
55
82
  ? task.cwd
56
83
  : path.resolve(process.cwd(), task.cwd);
57
84
  const localBinPath = path.join(taskCwd, "node_modules", ".bin");
58
- // Build PATH with local node_modules/.bin first (as absolute path), then existing PATH
85
+ // Build PATH with local node_modules/.bin first
59
86
  const existingPath = process.env.PATH || process.env.Path || "";
60
87
  const pathSeparator = process.platform === "win32" ? ";" : ":";
61
- // Collect all potential bin paths (local first, then root, then existing)
62
88
  const binPaths = [localBinPath];
63
- // Also add root node_modules/.bin if different from local
64
89
  const rootBinPath = path.join(process.cwd(), "node_modules", ".bin");
65
90
  if (rootBinPath !== localBinPath) {
66
91
  binPaths.push(rootBinPath);
67
92
  }
68
- // Add existing PATH
69
93
  if (existingPath) {
70
94
  binPaths.push(existingPath);
71
95
  }
@@ -73,10 +97,9 @@ export function pipeline(tasks) {
73
97
  const env = {
74
98
  ...process.env,
75
99
  PATH: newPath,
76
- Path: newPath, // Windows uses both PATH and Path
100
+ Path: newPath,
77
101
  };
78
- // Use shell mode to allow commands like "vite dev" to work properly
79
- const child = spawn(task.command, {
102
+ const child = spawn(command, {
80
103
  cwd: task.cwd,
81
104
  stdio: ["inherit", "pipe", "pipe"],
82
105
  shell: true,
@@ -87,35 +110,37 @@ export function pipeline(tasks) {
87
110
  console.error(`[${task.name}] Failed to start:`, error.message);
88
111
  runningTasks.delete(task.name);
89
112
  completedTasks.add(task.name);
113
+ task._markComplete();
90
114
  process.exitCode = 1;
91
115
  eventEmitter.emit("taskCompleted", task.name);
92
116
  });
93
117
  runningTasks.set(task.name, child);
94
- // Parse events from stdout
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
95
125
  let stdoutBuffer = "";
126
+ let allOutput = "";
96
127
  child.stdout?.on("data", (data) => {
97
- stdoutBuffer += data.toString();
128
+ const chunk = data.toString();
129
+ allOutput += chunk;
130
+ stdoutBuffer += chunk;
98
131
  const lines = stdoutBuffer.split("\n");
99
132
  stdoutBuffer = lines.pop() || "";
100
133
  for (const line of lines) {
101
- if (line.startsWith(EVENT_PREFIX)) {
102
- try {
103
- const eventData = JSON.parse(line.slice(EVENT_PREFIX.length));
104
- const eventName = eventData.event;
105
- if (eventName && !emittedEvents.has(eventName)) {
106
- emittedEvents.add(eventName);
107
- eventEmitter.emit("event", eventName);
108
- }
109
- }
110
- catch (e) {
111
- // Ignore parse errors
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);
112
140
  }
113
- // Don't forward event lines to stdout
114
- }
115
- else {
116
- // Forward stdout to parent
117
- process.stdout.write(line + "\n");
118
141
  }
142
+ // Forward stdout to parent
143
+ process.stdout.write(line + "\n");
119
144
  }
120
145
  });
121
146
  // Forward any remaining buffer on end
@@ -132,34 +157,26 @@ export function pipeline(tasks) {
132
157
  child.on("exit", (code) => {
133
158
  runningTasks.delete(task.name);
134
159
  completedTasks.add(task.name);
160
+ task._markComplete();
135
161
  if (code !== 0) {
136
162
  process.exitCode = code || 1;
137
163
  }
138
- // Check if any waiting tasks can now start
139
164
  eventEmitter.emit("taskCompleted", task.name);
140
165
  });
141
166
  };
142
- // Listen for events and task completions to start new tasks
143
- eventEmitter.on("event", () => {
144
- for (const task of tasks) {
145
- if (canStart(task)) {
146
- startTask(task);
147
- }
148
- }
149
- });
150
- eventEmitter.on("taskCompleted", () => {
167
+ // Function to try starting tasks when dependencies are ready
168
+ const tryStartTasks = async () => {
151
169
  for (const task of tasks) {
152
- if (canStart(task)) {
153
- startTask(task);
170
+ if (await canStart(task)) {
171
+ await startTask(task);
154
172
  }
155
173
  }
156
- });
174
+ };
175
+ // Listen for task readiness and completion to start dependent tasks
176
+ eventEmitter.on("taskReady", tryStartTasks);
177
+ eventEmitter.on("taskCompleted", tryStartTasks);
157
178
  // Start tasks that don't have dependencies
158
- for (const task of tasks) {
159
- if (canStart(task)) {
160
- startTask(task);
161
- }
162
- }
179
+ await tryStartTasks();
163
180
  // Wait for all tasks to complete
164
181
  return new Promise((resolve, reject) => {
165
182
  const checkCompletion = () => {
@@ -168,17 +185,15 @@ export function pipeline(tasks) {
168
185
  }
169
186
  };
170
187
  eventEmitter.on("taskCompleted", checkCompletion);
171
- checkCompletion(); // Check immediately in case all tasks completed synchronously
188
+ checkCompletion();
172
189
  // Handle process termination
173
190
  process.on("SIGINT", () => {
174
- // Kill all running tasks
175
191
  for (const child of runningTasks.values()) {
176
192
  child.kill("SIGINT");
177
193
  }
178
194
  reject(new Error("Process interrupted"));
179
195
  });
180
196
  process.on("SIGTERM", () => {
181
- // Kill all running tasks
182
197
  for (const child of runningTasks.values()) {
183
198
  child.kill("SIGTERM");
184
199
  }
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "builderman",
3
- "version": "1.0.2",
4
- "description": "An awesome new package",
3
+ "version": "1.0.3",
4
+ "description": "Simple task runner for building and developing projects.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/LankyMoose/builderman"
10
10
  },
11
- "keywords": [],
11
+ "keywords": [
12
+ "task",
13
+ "runner",
14
+ "build",
15
+ "development",
16
+ "pipeline"
17
+ ],
12
18
  "author": "LankyMoose",
13
19
  "license": "ISC",
14
20
  "devDependencies": {