builderman 1.0.1 → 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,70 +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
+ }
26
+ };
27
+ task._markComplete = () => {
28
+ if (!isComplete) {
29
+ isComplete = true;
30
+ if (!isReady && resolveReady) {
31
+ isReady = true;
32
+ resolveReady();
33
+ }
34
+ }
22
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
  }
79
+ const command = getCommand(task);
51
80
  // Ensure node_modules/.bin is in PATH for local dependencies
52
- // Resolve cwd relative to current working directory
53
81
  const taskCwd = path.isAbsolute(task.cwd)
54
82
  ? task.cwd
55
83
  : path.resolve(process.cwd(), task.cwd);
56
84
  const localBinPath = path.join(taskCwd, "node_modules", ".bin");
57
- // Build PATH with local node_modules/.bin first (as absolute path), then existing PATH
85
+ // Build PATH with local node_modules/.bin first
58
86
  const existingPath = process.env.PATH || process.env.Path || "";
59
87
  const pathSeparator = process.platform === "win32" ? ";" : ":";
60
- // Collect all potential bin paths (local first, then root, then existing)
61
88
  const binPaths = [localBinPath];
62
- // Also add root node_modules/.bin if different from local
63
89
  const rootBinPath = path.join(process.cwd(), "node_modules", ".bin");
64
90
  if (rootBinPath !== localBinPath) {
65
91
  binPaths.push(rootBinPath);
66
92
  }
67
- // Add existing PATH
68
93
  if (existingPath) {
69
94
  binPaths.push(existingPath);
70
95
  }
@@ -72,41 +97,50 @@ export function pipeline(tasks) {
72
97
  const env = {
73
98
  ...process.env,
74
99
  PATH: newPath,
75
- Path: newPath, // Windows uses both PATH and Path
100
+ Path: newPath,
76
101
  };
77
- // Use shell mode to allow commands like "vite dev" to work properly
78
- const child = spawn(task.command, {
102
+ const child = spawn(command, {
79
103
  cwd: task.cwd,
80
104
  stdio: ["inherit", "pipe", "pipe"],
81
105
  shell: true,
82
106
  env,
83
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
+ });
84
117
  runningTasks.set(task.name, child);
85
- // 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
86
125
  let stdoutBuffer = "";
126
+ let allOutput = "";
87
127
  child.stdout?.on("data", (data) => {
88
- stdoutBuffer += data.toString();
128
+ const chunk = data.toString();
129
+ allOutput += chunk;
130
+ stdoutBuffer += chunk;
89
131
  const lines = stdoutBuffer.split("\n");
90
132
  stdoutBuffer = lines.pop() || "";
91
133
  for (const line of lines) {
92
- if (line.startsWith(EVENT_PREFIX)) {
93
- try {
94
- const eventData = JSON.parse(line.slice(EVENT_PREFIX.length));
95
- const eventName = eventData.event;
96
- if (eventName && !emittedEvents.has(eventName)) {
97
- emittedEvents.add(eventName);
98
- eventEmitter.emit("event", eventName);
99
- }
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);
100
140
  }
101
- catch (e) {
102
- // Ignore parse errors
103
- }
104
- // Don't forward event lines to stdout
105
- }
106
- else {
107
- // Forward stdout to parent
108
- process.stdout.write(line + "\n");
109
141
  }
142
+ // Forward stdout to parent
143
+ process.stdout.write(line + "\n");
110
144
  }
111
145
  });
112
146
  // Forward any remaining buffer on end
@@ -123,34 +157,26 @@ export function pipeline(tasks) {
123
157
  child.on("exit", (code) => {
124
158
  runningTasks.delete(task.name);
125
159
  completedTasks.add(task.name);
160
+ task._markComplete();
126
161
  if (code !== 0) {
127
162
  process.exitCode = code || 1;
128
163
  }
129
- // Check if any waiting tasks can now start
130
164
  eventEmitter.emit("taskCompleted", task.name);
131
165
  });
132
166
  };
133
- // Listen for events and task completions to start new tasks
134
- eventEmitter.on("event", () => {
135
- for (const task of tasks) {
136
- if (canStart(task)) {
137
- startTask(task);
138
- }
139
- }
140
- });
141
- eventEmitter.on("taskCompleted", () => {
167
+ // Function to try starting tasks when dependencies are ready
168
+ const tryStartTasks = async () => {
142
169
  for (const task of tasks) {
143
- if (canStart(task)) {
144
- startTask(task);
170
+ if (await canStart(task)) {
171
+ await startTask(task);
145
172
  }
146
173
  }
147
- });
174
+ };
175
+ // Listen for task readiness and completion to start dependent tasks
176
+ eventEmitter.on("taskReady", tryStartTasks);
177
+ eventEmitter.on("taskCompleted", tryStartTasks);
148
178
  // Start tasks that don't have dependencies
149
- for (const task of tasks) {
150
- if (canStart(task)) {
151
- startTask(task);
152
- }
153
- }
179
+ await tryStartTasks();
154
180
  // Wait for all tasks to complete
155
181
  return new Promise((resolve, reject) => {
156
182
  const checkCompletion = () => {
@@ -159,17 +185,15 @@ export function pipeline(tasks) {
159
185
  }
160
186
  };
161
187
  eventEmitter.on("taskCompleted", checkCompletion);
162
- checkCompletion(); // Check immediately in case all tasks completed synchronously
188
+ checkCompletion();
163
189
  // Handle process termination
164
190
  process.on("SIGINT", () => {
165
- // Kill all running tasks
166
191
  for (const child of runningTasks.values()) {
167
192
  child.kill("SIGINT");
168
193
  }
169
194
  reject(new Error("Process interrupted"));
170
195
  });
171
196
  process.on("SIGTERM", () => {
172
- // Kill all running tasks
173
197
  for (const child of runningTasks.values()) {
174
198
  child.kill("SIGTERM");
175
199
  }
package/package.json CHANGED
@@ -1,14 +1,20 @@
1
1
  {
2
2
  "name": "builderman",
3
- "version": "1.0.1",
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": {