builderman 1.0.2 → 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,25 +1,3 @@
1
- export interface TaskConfig {
2
- name: string;
3
- command: string;
4
- cwd: string;
5
- requiresEvents?: string[];
6
- }
7
- export interface Task extends TaskConfig {
8
- requiresEvents: string[];
9
- }
10
- export interface Pipeline {
11
- run(): Promise<void>;
12
- }
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
- * Creates a task configuration.
20
- */
21
- export declare function task(config: TaskConfig): Task;
22
- /**
23
- * Creates a pipeline that manages task execution with event-based dependencies.
24
- */
25
- 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,190 +1,2 @@
1
- import { spawn } from "node:child_process";
2
- import { EventEmitter } from "node:events";
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
- /**
14
- * Creates a task configuration.
15
- */
16
- export function task(config) {
17
- return {
18
- name: config.name,
19
- command: config.command,
20
- cwd: config.cwd,
21
- requiresEvents: config.requiresEvents || [],
22
- };
23
- }
24
- /**
25
- * Creates a pipeline that manages task execution with event-based dependencies.
26
- */
27
- export function pipeline(tasks) {
28
- return {
29
- async run() {
30
- const eventEmitter = new EventEmitter();
31
- const emittedEvents = new Set();
32
- const runningTasks = new Map();
33
- 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) => {
41
- if (runningTasks.has(task.name) || completedTasks.has(task.name)) {
42
- return false;
43
- }
44
- return task.requiresEvents.every((event) => emittedEvents.has(event));
45
- };
46
- // Function to start a task
47
- const startTask = (task) => {
48
- if (runningTasks.has(task.name)) {
49
- return;
50
- }
51
- console.error(`[builderman] Starting task: ${task.name}`);
52
- // Ensure node_modules/.bin is in PATH for local dependencies
53
- // Resolve cwd relative to current working directory
54
- const taskCwd = path.isAbsolute(task.cwd)
55
- ? task.cwd
56
- : path.resolve(process.cwd(), task.cwd);
57
- const localBinPath = path.join(taskCwd, "node_modules", ".bin");
58
- // Build PATH with local node_modules/.bin first (as absolute path), then existing PATH
59
- const existingPath = process.env.PATH || process.env.Path || "";
60
- const pathSeparator = process.platform === "win32" ? ";" : ":";
61
- // Collect all potential bin paths (local first, then root, then existing)
62
- const binPaths = [localBinPath];
63
- // Also add root node_modules/.bin if different from local
64
- const rootBinPath = path.join(process.cwd(), "node_modules", ".bin");
65
- if (rootBinPath !== localBinPath) {
66
- binPaths.push(rootBinPath);
67
- }
68
- // Add existing PATH
69
- if (existingPath) {
70
- binPaths.push(existingPath);
71
- }
72
- const newPath = binPaths.join(pathSeparator);
73
- const env = {
74
- ...process.env,
75
- PATH: newPath,
76
- Path: newPath, // Windows uses both PATH and Path
77
- };
78
- // Use shell mode to allow commands like "vite dev" to work properly
79
- const child = spawn(task.command, {
80
- cwd: task.cwd,
81
- stdio: ["inherit", "pipe", "pipe"],
82
- shell: true,
83
- env,
84
- });
85
- // Handle spawn errors
86
- child.on("error", (error) => {
87
- console.error(`[${task.name}] Failed to start:`, error.message);
88
- runningTasks.delete(task.name);
89
- completedTasks.add(task.name);
90
- process.exitCode = 1;
91
- eventEmitter.emit("taskCompleted", task.name);
92
- });
93
- runningTasks.set(task.name, child);
94
- // Parse events from stdout
95
- let stdoutBuffer = "";
96
- child.stdout?.on("data", (data) => {
97
- stdoutBuffer += data.toString();
98
- const lines = stdoutBuffer.split("\n");
99
- stdoutBuffer = lines.pop() || "";
100
- 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
112
- }
113
- // Don't forward event lines to stdout
114
- }
115
- else {
116
- // Forward stdout to parent
117
- process.stdout.write(line + "\n");
118
- }
119
- }
120
- });
121
- // Forward any remaining buffer on end
122
- child.stdout?.on("end", () => {
123
- if (stdoutBuffer) {
124
- process.stdout.write(stdoutBuffer);
125
- }
126
- });
127
- // Forward stderr
128
- child.stderr?.on("data", (data) => {
129
- process.stderr.write(data);
130
- });
131
- // Handle task completion
132
- child.on("exit", (code) => {
133
- runningTasks.delete(task.name);
134
- completedTasks.add(task.name);
135
- if (code !== 0) {
136
- process.exitCode = code || 1;
137
- }
138
- // Check if any waiting tasks can now start
139
- eventEmitter.emit("taskCompleted", task.name);
140
- });
141
- };
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", () => {
151
- for (const task of tasks) {
152
- if (canStart(task)) {
153
- startTask(task);
154
- }
155
- }
156
- });
157
- // Start tasks that don't have dependencies
158
- for (const task of tasks) {
159
- if (canStart(task)) {
160
- startTask(task);
161
- }
162
- }
163
- // Wait for all tasks to complete
164
- return new Promise((resolve, reject) => {
165
- const checkCompletion = () => {
166
- if (runningTasks.size === 0) {
167
- resolve();
168
- }
169
- };
170
- eventEmitter.on("taskCompleted", checkCompletion);
171
- checkCompletion(); // Check immediately in case all tasks completed synchronously
172
- // Handle process termination
173
- process.on("SIGINT", () => {
174
- // Kill all running tasks
175
- for (const child of runningTasks.values()) {
176
- child.kill("SIGINT");
177
- }
178
- reject(new Error("Process interrupted"));
179
- });
180
- process.on("SIGTERM", () => {
181
- // Kill all running tasks
182
- for (const child of runningTasks.values()) {
183
- child.kill("SIGTERM");
184
- }
185
- reject(new Error("Process terminated"));
186
- });
187
- });
188
- },
189
- };
190
- }
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,14 +1,20 @@
1
1
  {
2
2
  "name": "builderman",
3
- "version": "1.0.2",
4
- "description": "An awesome new package",
3
+ "version": "1.0.4",
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": {