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 +10 -9
- package/dist/index.js +88 -73
- package/package.json +9 -3
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
|
-
|
|
7
|
+
commands: Commands;
|
|
4
8
|
cwd: string;
|
|
5
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
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
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
100
|
+
Path: newPath,
|
|
77
101
|
};
|
|
78
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
//
|
|
143
|
-
|
|
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
|
-
|
|
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();
|
|
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.
|
|
4
|
-
"description": "
|
|
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": {
|