builderman 1.0.0
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/LICENSE +7 -0
- package/README.md +5 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +169 -0
- package/package.json +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present, LankyMoose
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface TaskConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
command: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PipelineTaskConfig {
|
|
7
|
+
name: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
command: string;
|
|
11
|
+
requiresEvents?: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface Task extends TaskConfig {
|
|
14
|
+
requiresEvents: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface Pipeline {
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Emits an event that can be received by the parent process.
|
|
21
|
+
* Events are written to stdout in a parseable format.
|
|
22
|
+
*/
|
|
23
|
+
export declare function emit(event: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Creates a task configuration.
|
|
26
|
+
*/
|
|
27
|
+
export declare function task(config: TaskConfig): Task;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a pipeline that manages task execution with event-based dependencies.
|
|
30
|
+
*/
|
|
31
|
+
export declare function pipeline(configs: (Task | PipelineTaskConfig)[]): Pipeline;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
const EVENT_PREFIX = "__BUILDERMAN_EVENT__:";
|
|
4
|
+
/**
|
|
5
|
+
* Emits an event that can be received by the parent process.
|
|
6
|
+
* Events are written to stdout in a parseable format.
|
|
7
|
+
*/
|
|
8
|
+
export function emit(event) {
|
|
9
|
+
const eventData = JSON.stringify({ event });
|
|
10
|
+
console.log(`${EVENT_PREFIX}${eventData}`);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a task configuration.
|
|
14
|
+
*/
|
|
15
|
+
export function task(config) {
|
|
16
|
+
return {
|
|
17
|
+
name: config.name,
|
|
18
|
+
command: config.command,
|
|
19
|
+
cwd: config.cwd,
|
|
20
|
+
requiresEvents: [],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a pipeline that manages task execution with event-based dependencies.
|
|
25
|
+
*/
|
|
26
|
+
export function pipeline(configs) {
|
|
27
|
+
const tasks = configs.map((config) => {
|
|
28
|
+
if ("requiresEvents" in config || "path" in config) {
|
|
29
|
+
// It's a PipelineTaskConfig
|
|
30
|
+
const taskConfig = config;
|
|
31
|
+
return {
|
|
32
|
+
name: taskConfig.name,
|
|
33
|
+
command: taskConfig.command,
|
|
34
|
+
cwd: taskConfig.path || taskConfig.cwd || process.cwd(),
|
|
35
|
+
requiresEvents: taskConfig.requiresEvents || [],
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// It's already a Task
|
|
40
|
+
return config;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
async run() {
|
|
45
|
+
const eventEmitter = new EventEmitter();
|
|
46
|
+
const emittedEvents = new Set();
|
|
47
|
+
const runningTasks = new Map();
|
|
48
|
+
const completedTasks = new Set();
|
|
49
|
+
const taskConfigs = new Map();
|
|
50
|
+
// Index tasks by name
|
|
51
|
+
for (const task of tasks) {
|
|
52
|
+
taskConfigs.set(task.name, task);
|
|
53
|
+
}
|
|
54
|
+
// Function to check if a task can start
|
|
55
|
+
const canStart = (task) => {
|
|
56
|
+
if (runningTasks.has(task.name) || completedTasks.has(task.name)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return task.requiresEvents.every((event) => emittedEvents.has(event));
|
|
60
|
+
};
|
|
61
|
+
// Function to start a task
|
|
62
|
+
const startTask = (task) => {
|
|
63
|
+
if (runningTasks.has(task.name)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const [command, ...args] = task.command.split(/\s+/);
|
|
67
|
+
const child = spawn(command, args, {
|
|
68
|
+
cwd: task.cwd,
|
|
69
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
70
|
+
shell: process.platform === "win32",
|
|
71
|
+
});
|
|
72
|
+
runningTasks.set(task.name, child);
|
|
73
|
+
// Parse events from stdout
|
|
74
|
+
let stdoutBuffer = "";
|
|
75
|
+
child.stdout?.on("data", (data) => {
|
|
76
|
+
stdoutBuffer += data.toString();
|
|
77
|
+
const lines = stdoutBuffer.split("\n");
|
|
78
|
+
stdoutBuffer = lines.pop() || "";
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
if (line.startsWith(EVENT_PREFIX)) {
|
|
81
|
+
try {
|
|
82
|
+
const eventData = JSON.parse(line.slice(EVENT_PREFIX.length));
|
|
83
|
+
const eventName = eventData.event;
|
|
84
|
+
if (eventName && !emittedEvents.has(eventName)) {
|
|
85
|
+
emittedEvents.add(eventName);
|
|
86
|
+
eventEmitter.emit("event", eventName);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
// Ignore parse errors
|
|
91
|
+
}
|
|
92
|
+
// Don't forward event lines to stdout
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
// Forward stdout to parent
|
|
96
|
+
process.stdout.write(line + "\n");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
// Forward any remaining buffer on end
|
|
101
|
+
child.stdout?.on("end", () => {
|
|
102
|
+
if (stdoutBuffer) {
|
|
103
|
+
process.stdout.write(stdoutBuffer);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
// Forward stderr
|
|
107
|
+
child.stderr?.on("data", (data) => {
|
|
108
|
+
process.stderr.write(data);
|
|
109
|
+
});
|
|
110
|
+
// Handle task completion
|
|
111
|
+
child.on("exit", (code) => {
|
|
112
|
+
runningTasks.delete(task.name);
|
|
113
|
+
completedTasks.add(task.name);
|
|
114
|
+
if (code !== 0) {
|
|
115
|
+
process.exitCode = code || 1;
|
|
116
|
+
}
|
|
117
|
+
// Check if any waiting tasks can now start
|
|
118
|
+
eventEmitter.emit("taskCompleted", task.name);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
// Listen for events and task completions to start new tasks
|
|
122
|
+
eventEmitter.on("event", () => {
|
|
123
|
+
for (const task of tasks) {
|
|
124
|
+
if (canStart(task)) {
|
|
125
|
+
startTask(task);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
eventEmitter.on("taskCompleted", () => {
|
|
130
|
+
for (const task of tasks) {
|
|
131
|
+
if (canStart(task)) {
|
|
132
|
+
startTask(task);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// Start tasks that don't have dependencies
|
|
137
|
+
for (const task of tasks) {
|
|
138
|
+
if (canStart(task)) {
|
|
139
|
+
startTask(task);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Wait for all tasks to complete
|
|
143
|
+
return new Promise((resolve, reject) => {
|
|
144
|
+
const checkCompletion = () => {
|
|
145
|
+
if (runningTasks.size === 0) {
|
|
146
|
+
resolve();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
eventEmitter.on("taskCompleted", checkCompletion);
|
|
150
|
+
checkCompletion(); // Check immediately in case all tasks completed synchronously
|
|
151
|
+
// Handle process termination
|
|
152
|
+
process.on("SIGINT", () => {
|
|
153
|
+
// Kill all running tasks
|
|
154
|
+
for (const child of runningTasks.values()) {
|
|
155
|
+
child.kill("SIGINT");
|
|
156
|
+
}
|
|
157
|
+
reject(new Error("Process interrupted"));
|
|
158
|
+
});
|
|
159
|
+
process.on("SIGTERM", () => {
|
|
160
|
+
// Kill all running tasks
|
|
161
|
+
for (const child of runningTasks.values()) {
|
|
162
|
+
child.kill("SIGTERM");
|
|
163
|
+
}
|
|
164
|
+
reject(new Error("Process terminated"));
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "builderman",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An awesome new package",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/LankyMoose/builderman"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "LankyMoose",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^25.0.8"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "echo \"Error: no test specified\"",
|
|
19
|
+
"build": "rm -rf dist && tsc",
|
|
20
|
+
"dev": "tsc --watch"
|
|
21
|
+
}
|
|
22
|
+
}
|