@bb-labs/bldr 0.0.3 → 0.0.5
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/README.md +20 -3
- package/dist/index.js +196 -12
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# bldr
|
|
2
2
|
|
|
3
|
-
A TypeScript build tool with watch mode
|
|
3
|
+
A TypeScript build tool with watch mode, automatic dist synchronization, and split-terminal UI.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -21,5 +21,22 @@ bun add -d @bb-labs/builder typescript tsc-alias
|
|
|
21
21
|
|
|
22
22
|
## Options
|
|
23
23
|
|
|
24
|
-
- `--watch`, `-w`: Watch mode
|
|
24
|
+
- `--watch`, `-w`: Watch mode with split-terminal UI showing tsc, tsc-alias, and bldr outputs side by side
|
|
25
25
|
- `--project`, `-p <path>`: Custom tsconfig.json path
|
|
26
|
+
|
|
27
|
+
## Testing
|
|
28
|
+
|
|
29
|
+
Run `bun run test:dev` to test the build tool with the included test files in `test-src/`. This will:
|
|
30
|
+
|
|
31
|
+
- Build files from `test-src/` to `test-dist/`
|
|
32
|
+
- Show the split-terminal UI with real-time updates
|
|
33
|
+
- Watch for changes and rebuild automatically
|
|
34
|
+
|
|
35
|
+
Add or modify files in `test-src/` to see the build tool in action!
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Split Terminal UI**: In watch mode, displays three panels side by side showing output from TypeScript compiler, tsc-alias, and bldr itself
|
|
40
|
+
- **Clean Builds**: Always cleans output directory before building
|
|
41
|
+
- **Path Alias Support**: Integrates with tsc-alias for path mapping
|
|
42
|
+
- **Process Management**: Properly handles child process cleanup on exit
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import chokidar from "chokidar";
|
|
6
|
+
import blessed from "blessed";
|
|
6
7
|
// We use the TypeScript API to correctly resolve tsconfig + "extends".
|
|
7
8
|
import ts from "typescript";
|
|
8
9
|
function parseArgs(argv) {
|
|
@@ -65,21 +66,39 @@ function run(cmd, args, cwd) {
|
|
|
65
66
|
return new Promise((resolve, reject) => {
|
|
66
67
|
const p = spawn(cmd, args, {
|
|
67
68
|
cwd,
|
|
68
|
-
stdio: "inherit",
|
|
69
|
+
stdio: ["inherit", "pipe", "pipe"], // pipe both stdout and stderr to capture all output
|
|
69
70
|
shell: process.platform === "win32",
|
|
70
71
|
});
|
|
72
|
+
let stdout = "";
|
|
73
|
+
let stderr = "";
|
|
74
|
+
if (p.stdout) {
|
|
75
|
+
p.stdout.on("data", (data) => {
|
|
76
|
+
stdout += data.toString();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (p.stderr) {
|
|
80
|
+
p.stderr.on("data", (data) => {
|
|
81
|
+
stderr += data.toString();
|
|
82
|
+
});
|
|
83
|
+
}
|
|
71
84
|
p.on("exit", (code) => {
|
|
72
85
|
if (code === 0)
|
|
73
86
|
resolve();
|
|
74
|
-
else
|
|
75
|
-
|
|
87
|
+
else {
|
|
88
|
+
// Give a small delay to ensure all output data is captured
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
const allOutput = (stdout + stderr).trim();
|
|
91
|
+
const errorMsg = allOutput || `${cmd} ${args.join(" ")} failed with exit code ${code}`;
|
|
92
|
+
reject(new Error(errorMsg));
|
|
93
|
+
}, 100);
|
|
94
|
+
}
|
|
76
95
|
});
|
|
77
96
|
});
|
|
78
97
|
}
|
|
79
98
|
function spawnLongRunning(cmd, args, cwd) {
|
|
80
99
|
const p = spawn(cmd, args, {
|
|
81
100
|
cwd,
|
|
82
|
-
stdio: "inherit",
|
|
101
|
+
stdio: ["inherit", "pipe", "pipe"], // pipe stdout and stderr
|
|
83
102
|
shell: process.platform === "win32",
|
|
84
103
|
});
|
|
85
104
|
return p;
|
|
@@ -133,6 +152,81 @@ async function main() {
|
|
|
133
152
|
}
|
|
134
153
|
const rootDirAbs = path.isAbsolute(cfg.rootDir) ? cfg.rootDir : path.resolve(cfg.configDir, cfg.rootDir);
|
|
135
154
|
const outDirAbs = path.isAbsolute(cfg.outDir) ? cfg.outDir : path.resolve(cfg.configDir, cfg.outDir);
|
|
155
|
+
// Create terminal UI only in watch mode
|
|
156
|
+
let screen, bldrBox, tscBox, aliasBox, logToBldr;
|
|
157
|
+
if (args.watch) {
|
|
158
|
+
screen = blessed.screen({
|
|
159
|
+
smartCSR: true,
|
|
160
|
+
title: "bldr - TypeScript Build Tool",
|
|
161
|
+
});
|
|
162
|
+
bldrBox = blessed.box({
|
|
163
|
+
top: 0,
|
|
164
|
+
left: 0,
|
|
165
|
+
width: "33%",
|
|
166
|
+
height: "100%",
|
|
167
|
+
label: " bldr ",
|
|
168
|
+
border: { type: "line" },
|
|
169
|
+
scrollable: true,
|
|
170
|
+
alwaysScroll: true,
|
|
171
|
+
scrollbar: { ch: " " },
|
|
172
|
+
});
|
|
173
|
+
tscBox = blessed.box({
|
|
174
|
+
top: 0,
|
|
175
|
+
left: "33%",
|
|
176
|
+
width: "34%",
|
|
177
|
+
height: "100%",
|
|
178
|
+
label: " tsc ",
|
|
179
|
+
border: { type: "line" },
|
|
180
|
+
scrollable: true,
|
|
181
|
+
alwaysScroll: true,
|
|
182
|
+
scrollbar: { ch: " " },
|
|
183
|
+
});
|
|
184
|
+
aliasBox = blessed.box({
|
|
185
|
+
top: 0,
|
|
186
|
+
left: "67%",
|
|
187
|
+
width: "33%",
|
|
188
|
+
height: "100%",
|
|
189
|
+
label: " tsc-alias ",
|
|
190
|
+
border: { type: "line" },
|
|
191
|
+
scrollable: true,
|
|
192
|
+
alwaysScroll: true,
|
|
193
|
+
scrollbar: { ch: " " },
|
|
194
|
+
});
|
|
195
|
+
screen.append(bldrBox);
|
|
196
|
+
screen.append(tscBox);
|
|
197
|
+
screen.append(aliasBox);
|
|
198
|
+
// Handle screen events
|
|
199
|
+
screen.key(["escape", "q", "C-c"], () => {
|
|
200
|
+
shutdown();
|
|
201
|
+
});
|
|
202
|
+
screen.key(["C-l"], () => {
|
|
203
|
+
bldrBox.setScrollPerc(100);
|
|
204
|
+
tscBox.setScrollPerc(100);
|
|
205
|
+
aliasBox.setScrollPerc(100);
|
|
206
|
+
screen.render();
|
|
207
|
+
});
|
|
208
|
+
// Create a function to log to the bldr box
|
|
209
|
+
logToBldr = (text) => {
|
|
210
|
+
if (screen && bldrBox) {
|
|
211
|
+
bldrBox.insertBottom(text);
|
|
212
|
+
bldrBox.setScrollPerc(100);
|
|
213
|
+
screen.render();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
// Override console.log and console.error for bldr messages
|
|
217
|
+
const originalLog = console.log;
|
|
218
|
+
const originalError = console.error;
|
|
219
|
+
console.log = (...args) => {
|
|
220
|
+
logToBldr(args.join(" "));
|
|
221
|
+
// Don't call originalLog to avoid double output
|
|
222
|
+
};
|
|
223
|
+
console.error = (...args) => {
|
|
224
|
+
logToBldr(args.join(" "));
|
|
225
|
+
// Don't call originalError to avoid double output
|
|
226
|
+
};
|
|
227
|
+
screen.render();
|
|
228
|
+
}
|
|
229
|
+
// Initial messages will now go to the bldr box via overridden console.log
|
|
136
230
|
console.log([
|
|
137
231
|
`\n[bldr] project: ${rel(cwd, tsconfigPath)}`,
|
|
138
232
|
`[bldr] rootDir : ${rel(cwd, rootDirAbs)}`,
|
|
@@ -144,6 +238,42 @@ async function main() {
|
|
|
144
238
|
await rmIfExists(outDirAbs);
|
|
145
239
|
const tscArgs = ["-p", tsconfigPath];
|
|
146
240
|
const aliasArgs = ["-p", tsconfigPath];
|
|
241
|
+
// Error handling function for watch mode
|
|
242
|
+
const handleFatalError = async (errorMessage) => {
|
|
243
|
+
// Always restore normal console logging for watch mode
|
|
244
|
+
if (args.watch) {
|
|
245
|
+
// Destroy UI first to avoid conflicts
|
|
246
|
+
if (screen) {
|
|
247
|
+
screen.destroy();
|
|
248
|
+
screen = null;
|
|
249
|
+
bldrBox = null;
|
|
250
|
+
tscBox = null;
|
|
251
|
+
aliasBox = null;
|
|
252
|
+
}
|
|
253
|
+
// Write directly to stderr to ensure visibility
|
|
254
|
+
process.stderr.write(`[bldr] ${errorMessage}\n`);
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// In build mode, just print normally
|
|
258
|
+
console.error(`[bldr] ${errorMessage}`);
|
|
259
|
+
}
|
|
260
|
+
// Kill all processes
|
|
261
|
+
if (tscWatch && !tscWatch.killed) {
|
|
262
|
+
tscWatch.kill();
|
|
263
|
+
}
|
|
264
|
+
if (aliasWatch && !aliasWatch.killed) {
|
|
265
|
+
aliasWatch.kill();
|
|
266
|
+
}
|
|
267
|
+
if (srcWatcher) {
|
|
268
|
+
try {
|
|
269
|
+
await srcWatcher.close();
|
|
270
|
+
}
|
|
271
|
+
catch (e) {
|
|
272
|
+
// ignore
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
process.exit(1);
|
|
276
|
+
};
|
|
147
277
|
if (!args.watch) {
|
|
148
278
|
// One-shot build: tsc -> tsc-alias
|
|
149
279
|
try {
|
|
@@ -156,22 +286,69 @@ async function main() {
|
|
|
156
286
|
await rmIfExists(outDirAbs);
|
|
157
287
|
throw error;
|
|
158
288
|
}
|
|
289
|
+
console.log(`[bldr] build completed successfully!`);
|
|
159
290
|
return;
|
|
160
291
|
}
|
|
161
292
|
// Watch mode:
|
|
162
|
-
//
|
|
293
|
+
// 2) Do a clean initial pass so dist is correct immediately.
|
|
163
294
|
try {
|
|
164
295
|
await run("tsc", tscArgs, cwd);
|
|
165
296
|
await run("tsc-alias", aliasArgs, cwd);
|
|
166
297
|
}
|
|
167
298
|
catch (error) {
|
|
168
|
-
|
|
169
|
-
// Continue in watch mode even if initial build fails
|
|
170
|
-
console.log(`[bldr] continuing in watch mode...`);
|
|
299
|
+
await handleFatalError(`[bldr] initial build failed: ${error}`);
|
|
171
300
|
}
|
|
172
|
-
//
|
|
301
|
+
// 3) Start watchers
|
|
173
302
|
const tscWatch = spawnLongRunning("tsc", [...tscArgs, "-w"], cwd);
|
|
174
303
|
const aliasWatch = spawnLongRunning("tsc-alias", [...aliasArgs, "-w"], cwd);
|
|
304
|
+
// Pipe output to boxes
|
|
305
|
+
if (tscWatch.stdout) {
|
|
306
|
+
tscWatch.stdout.on("data", (data) => {
|
|
307
|
+
const text = data.toString().trim();
|
|
308
|
+
if (text) {
|
|
309
|
+
tscBox.insertBottom(text);
|
|
310
|
+
tscBox.setScrollPerc(100);
|
|
311
|
+
screen.render();
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (tscWatch.stderr) {
|
|
316
|
+
tscWatch.stderr.on("data", (data) => {
|
|
317
|
+
const text = data.toString().trim();
|
|
318
|
+
if (text) {
|
|
319
|
+
tscBox.insertBottom(text);
|
|
320
|
+
tscBox.setScrollPerc(100);
|
|
321
|
+
screen.render();
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (aliasWatch.stdout) {
|
|
326
|
+
aliasWatch.stdout.on("data", (data) => {
|
|
327
|
+
const text = data.toString().trim();
|
|
328
|
+
if (text) {
|
|
329
|
+
aliasBox.insertBottom(text);
|
|
330
|
+
aliasBox.setScrollPerc(100);
|
|
331
|
+
screen.render();
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
if (aliasWatch.stderr) {
|
|
336
|
+
aliasWatch.stderr.on("data", (data) => {
|
|
337
|
+
const text = data.toString().trim();
|
|
338
|
+
if (text) {
|
|
339
|
+
aliasBox.insertBottom(text);
|
|
340
|
+
aliasBox.setScrollPerc(100);
|
|
341
|
+
screen.render();
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
// If any child process errors or exits, shut down all processes
|
|
346
|
+
tscWatch.on("error", (error) => {
|
|
347
|
+
handleFatalError(`[bldr] tsc watcher error: ${error}`);
|
|
348
|
+
});
|
|
349
|
+
aliasWatch.on("error", (error) => {
|
|
350
|
+
handleFatalError(`[bldr] tsc-alias watcher error: ${error}`);
|
|
351
|
+
});
|
|
175
352
|
// 3) dist sync watcher: remove stale outputs on deletes/dir deletes
|
|
176
353
|
const cleaner = makeDistCleaner(rootDirAbs, outDirAbs);
|
|
177
354
|
const srcWatcher = chokidar.watch(rootDirAbs, {
|
|
@@ -225,9 +402,16 @@ async function main() {
|
|
|
225
402
|
// TypeScript compiler handles additions and changes automatically
|
|
226
403
|
const shutdown = async () => {
|
|
227
404
|
console.log("\n[bldr] shutting down...");
|
|
405
|
+
// Destroy screen if it exists
|
|
406
|
+
if (screen) {
|
|
407
|
+
screen.destroy();
|
|
408
|
+
screen = null;
|
|
409
|
+
}
|
|
228
410
|
// Close file watcher
|
|
229
411
|
try {
|
|
230
|
-
|
|
412
|
+
if (srcWatcher) {
|
|
413
|
+
await srcWatcher.close();
|
|
414
|
+
}
|
|
231
415
|
console.log("[bldr] src watcher closed");
|
|
232
416
|
}
|
|
233
417
|
catch (error) {
|
|
@@ -235,7 +419,7 @@ async function main() {
|
|
|
235
419
|
}
|
|
236
420
|
// Kill child processes
|
|
237
421
|
const killPromises = [];
|
|
238
|
-
if (!tscWatch.killed) {
|
|
422
|
+
if (tscWatch && !tscWatch.killed) {
|
|
239
423
|
killPromises.push(new Promise((resolve) => {
|
|
240
424
|
tscWatch.on("exit", () => resolve());
|
|
241
425
|
tscWatch.kill();
|
|
@@ -249,7 +433,7 @@ async function main() {
|
|
|
249
433
|
}));
|
|
250
434
|
console.log("[bldr] killing tsc watcher...");
|
|
251
435
|
}
|
|
252
|
-
if (!aliasWatch.killed) {
|
|
436
|
+
if (aliasWatch && !aliasWatch.killed) {
|
|
253
437
|
killPromises.push(new Promise((resolve) => {
|
|
254
438
|
aliasWatch.on("exit", () => resolve());
|
|
255
439
|
aliasWatch.kill();
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bb-labs/bldr",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"bin": {
|
|
5
6
|
"bldr": "./dist/index.js"
|
|
6
7
|
},
|
|
@@ -9,9 +10,12 @@
|
|
|
9
10
|
],
|
|
10
11
|
"scripts": {
|
|
11
12
|
"build": "tsc",
|
|
12
|
-
"postbuild": "chmod +x dist/index.js"
|
|
13
|
+
"postbuild": "chmod +x dist/index.js",
|
|
14
|
+
"test:build": "node dist/index.js --project test-src/tsconfig.json",
|
|
15
|
+
"test:watch": "node dist/index.js --watch --project test-src/tsconfig.json"
|
|
13
16
|
},
|
|
14
17
|
"devDependencies": {
|
|
18
|
+
"@types/blessed": "^0.1.27",
|
|
15
19
|
"@types/bun": "latest",
|
|
16
20
|
"@types/node": "latest"
|
|
17
21
|
},
|
|
@@ -20,6 +24,7 @@
|
|
|
20
24
|
},
|
|
21
25
|
"dependencies": {
|
|
22
26
|
"@bb-labs/tsconfigs": "^0.0.1",
|
|
27
|
+
"blessed": "^0.1.81",
|
|
23
28
|
"chokidar": "^5.0.0",
|
|
24
29
|
"tsc-alias": "^1.8.8"
|
|
25
30
|
}
|