@forklaunch/bunrun 0.0.1
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 +21 -0
- package/README.md +83 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +378 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 forklaunch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# @forklaunch/bunrun
|
|
2
|
+
|
|
3
|
+
A Bun-native TypeScript workspace script runner with topological ordering.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @forklaunch/bunrun
|
|
9
|
+
# or
|
|
10
|
+
bun add -g @forklaunch/bunrun
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Requirements:** Bun >= 1.1.0
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Run build script in topological order
|
|
19
|
+
bunrun build
|
|
20
|
+
|
|
21
|
+
# Run with specific script
|
|
22
|
+
bunrun test
|
|
23
|
+
|
|
24
|
+
# Run with concurrency limit
|
|
25
|
+
bunrun build --jobs 4
|
|
26
|
+
|
|
27
|
+
# Run sequentially (one at a time)
|
|
28
|
+
bunrun build --sequential
|
|
29
|
+
|
|
30
|
+
# Filter packages
|
|
31
|
+
bunrun build --filter "@myorg/*"
|
|
32
|
+
bunrun build --exclude "**/legacy-*"
|
|
33
|
+
|
|
34
|
+
# Debug mode
|
|
35
|
+
bunrun build --debug
|
|
36
|
+
|
|
37
|
+
# Print plan without executing
|
|
38
|
+
bunrun build --print-only
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- **Topological ordering**: Runs scripts in dependency order
|
|
44
|
+
- **Parallel execution**: Runs independent packages concurrently
|
|
45
|
+
- **Workspace discovery**: Automatically finds packages in monorepos
|
|
46
|
+
- **Flexible filtering**: Include/exclude packages by name or path
|
|
47
|
+
- **Dependency-aware**: Considers dependencies, devDependencies, and peerDependencies
|
|
48
|
+
- **Bun-native**: Direct TypeScript execution without compilation
|
|
49
|
+
|
|
50
|
+
## Options
|
|
51
|
+
|
|
52
|
+
- `script` - Script to run (default: build)
|
|
53
|
+
- `-j, --jobs <n>` - Concurrency per tier (default: CPU count)
|
|
54
|
+
- `--sequential` - Run strictly one-by-one in topological order
|
|
55
|
+
- `--filter <glob>` - Include packages matching glob pattern (can repeat)
|
|
56
|
+
- `--exclude <glob>` - Exclude packages matching glob pattern (can repeat)
|
|
57
|
+
- `--no-dev` - Ignore devDependencies for ordering
|
|
58
|
+
- `--no-peer` - Ignore peerDependencies for ordering
|
|
59
|
+
- `--print-only` - Print execution plan without running
|
|
60
|
+
- `--debug` - Show diagnostic information
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Build all packages in dependency order
|
|
66
|
+
bunrun build
|
|
67
|
+
|
|
68
|
+
# Run tests with limited concurrency
|
|
69
|
+
bunrun test --jobs 2
|
|
70
|
+
|
|
71
|
+
# Build only frontend packages
|
|
72
|
+
bunrun build --filter "**/frontend-*"
|
|
73
|
+
|
|
74
|
+
# Build everything except legacy packages
|
|
75
|
+
bunrun build --exclude "**/legacy-*"
|
|
76
|
+
|
|
77
|
+
# Sequential build for debugging
|
|
78
|
+
bunrun build --sequential --debug
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { readFileSync, readdirSync, statSync } from "fs";
|
|
5
|
+
import { availableParallelism, cpus } from "os";
|
|
6
|
+
import { join, relative, resolve, sep } from "path";
|
|
7
|
+
var cwd = process.cwd();
|
|
8
|
+
function detectCPUCount() {
|
|
9
|
+
try {
|
|
10
|
+
return Math.max(1, availableParallelism());
|
|
11
|
+
} catch {
|
|
12
|
+
try {
|
|
13
|
+
return Math.max(1, cpus()?.length ?? 4);
|
|
14
|
+
} catch {
|
|
15
|
+
return 4;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function parseArgs() {
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
const opts = {
|
|
22
|
+
script: "build",
|
|
23
|
+
jobs: detectCPUCount(),
|
|
24
|
+
includeDev: true,
|
|
25
|
+
includePeer: true,
|
|
26
|
+
only: [],
|
|
27
|
+
exclude: [],
|
|
28
|
+
printOnly: false,
|
|
29
|
+
sequential: false,
|
|
30
|
+
debug: false
|
|
31
|
+
};
|
|
32
|
+
let startIndex = 0;
|
|
33
|
+
if (args.length > 0 && args[0] && !args[0].startsWith("-")) {
|
|
34
|
+
opts.script = args[0];
|
|
35
|
+
startIndex = 1;
|
|
36
|
+
}
|
|
37
|
+
for (let i = startIndex; i < args.length; i++) {
|
|
38
|
+
const a = args[i];
|
|
39
|
+
if (!a) continue;
|
|
40
|
+
if (a === "-j" || a === "--jobs") {
|
|
41
|
+
const nextArg = args[++i];
|
|
42
|
+
if (!nextArg) {
|
|
43
|
+
console.error(`Missing value for ${a}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const jobs = Number(nextArg);
|
|
47
|
+
if (isNaN(jobs) || jobs < 1) {
|
|
48
|
+
console.error(`Invalid jobs value: ${nextArg}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
opts.jobs = jobs;
|
|
52
|
+
} else if (a === "--no-dev") {
|
|
53
|
+
opts.includeDev = false;
|
|
54
|
+
} else if (a === "--no-peer") {
|
|
55
|
+
opts.includePeer = false;
|
|
56
|
+
} else if (a === "--filter" || a === "--only") {
|
|
57
|
+
const nextArg = args[++i];
|
|
58
|
+
if (!nextArg) {
|
|
59
|
+
console.error(`Missing value for ${a}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
opts.only.push(nextArg);
|
|
63
|
+
} else if (a === "--exclude") {
|
|
64
|
+
const nextArg = args[++i];
|
|
65
|
+
if (!nextArg) {
|
|
66
|
+
console.error(`Missing value for ${a}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
opts.exclude.push(nextArg);
|
|
70
|
+
} else if (a === "--print-only") {
|
|
71
|
+
opts.printOnly = true;
|
|
72
|
+
} else if (a === "--sequential") {
|
|
73
|
+
opts.sequential = true;
|
|
74
|
+
} else if (a === "--debug") {
|
|
75
|
+
opts.debug = true;
|
|
76
|
+
} else {
|
|
77
|
+
console.error(`Unknown arg: ${a}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return opts;
|
|
82
|
+
}
|
|
83
|
+
function readJSON(p) {
|
|
84
|
+
try {
|
|
85
|
+
const content = readFileSync(p, "utf8");
|
|
86
|
+
const parsed = JSON.parse(content);
|
|
87
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
88
|
+
throw new Error(`Invalid JSON structure in ${p}`);
|
|
89
|
+
}
|
|
90
|
+
const pkg = parsed;
|
|
91
|
+
if (typeof pkg.name !== "string") {
|
|
92
|
+
throw new Error(`Missing or invalid name field in ${p}`);
|
|
93
|
+
}
|
|
94
|
+
return pkg;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error instanceof Error) {
|
|
97
|
+
throw new Error(`Failed to read package.json at ${p}: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function isDir(p) {
|
|
103
|
+
try {
|
|
104
|
+
return statSync(p).isDirectory();
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function safeReadDir(d) {
|
|
110
|
+
try {
|
|
111
|
+
return readdirSync(d);
|
|
112
|
+
} catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function matchGlob(s, glob) {
|
|
117
|
+
const esc = glob.replace(/[-/\\^$+?.()|[\]{}]/g, "\\$&").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*");
|
|
118
|
+
const re = new RegExp("^" + esc + "$");
|
|
119
|
+
return re.test(s);
|
|
120
|
+
}
|
|
121
|
+
function expandGlob(root, pattern) {
|
|
122
|
+
const abs = resolve(root, pattern);
|
|
123
|
+
const parts = relative(root, abs).split(sep);
|
|
124
|
+
const out = /* @__PURE__ */ new Set();
|
|
125
|
+
function walk(dir, i) {
|
|
126
|
+
if (i === parts.length) {
|
|
127
|
+
out.add(dir);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const seg = parts[i];
|
|
131
|
+
if (seg === "**") {
|
|
132
|
+
walk(dir, i + 1);
|
|
133
|
+
for (const e of safeReadDir(dir)) {
|
|
134
|
+
const p = join(dir, e);
|
|
135
|
+
if (isDir(p)) walk(p, i);
|
|
136
|
+
}
|
|
137
|
+
} else if (seg === "*") {
|
|
138
|
+
for (const e of safeReadDir(dir)) {
|
|
139
|
+
const p = join(dir, e);
|
|
140
|
+
if (isDir(p)) walk(p, i + 1);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
const next = join(dir, seg);
|
|
144
|
+
if (isDir(next)) walk(next, i + 1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
walk(root, 0);
|
|
148
|
+
return [...out];
|
|
149
|
+
}
|
|
150
|
+
function getWorkspaceDirs(rootPkg) {
|
|
151
|
+
const ws = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : rootPkg.workspaces?.packages || [];
|
|
152
|
+
const dirs = [];
|
|
153
|
+
for (const patt of ws) {
|
|
154
|
+
if (typeof patt !== "string") {
|
|
155
|
+
console.warn(`Skipping invalid workspace pattern: ${patt}`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
for (const d of expandGlob(cwd, patt)) {
|
|
159
|
+
try {
|
|
160
|
+
readJSON(join(d, "package.json"));
|
|
161
|
+
dirs.push(d);
|
|
162
|
+
} catch {
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return [...new Set(dirs)];
|
|
167
|
+
}
|
|
168
|
+
function collectDeps(pkgJson, includeDev, includePeer) {
|
|
169
|
+
return {
|
|
170
|
+
...pkgJson.dependencies || {},
|
|
171
|
+
...pkgJson.optionalDependencies || {},
|
|
172
|
+
...includeDev ? pkgJson.devDependencies || {} : {},
|
|
173
|
+
...includePeer ? pkgJson.peerDependencies || {} : {}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function topoTiers(nodes, edges) {
|
|
177
|
+
const indeg = new Map(nodes.map((n) => [n, 0]));
|
|
178
|
+
for (const [, outs] of edges) {
|
|
179
|
+
for (const m of outs) {
|
|
180
|
+
indeg.set(m, (indeg.get(m) || 0) + 1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const remain = new Set(nodes);
|
|
184
|
+
const tiers = [];
|
|
185
|
+
while (remain.size > 0) {
|
|
186
|
+
const zero = [];
|
|
187
|
+
for (const n of remain) {
|
|
188
|
+
if ((indeg.get(n) || 0) === 0) {
|
|
189
|
+
zero.push(n);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (zero.length === 0) {
|
|
193
|
+
console.warn(
|
|
194
|
+
"Dependency cycle detected, placing remaining packages in final tier"
|
|
195
|
+
);
|
|
196
|
+
tiers.push([...remain]);
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
tiers.push(zero);
|
|
200
|
+
for (const z of zero) {
|
|
201
|
+
remain.delete(z);
|
|
202
|
+
const outs = edges.get(z);
|
|
203
|
+
if (!outs) continue;
|
|
204
|
+
for (const m of outs) {
|
|
205
|
+
indeg.set(m, Math.max(0, (indeg.get(m) || 0) - 1));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return tiers;
|
|
210
|
+
}
|
|
211
|
+
async function runSequential(dirsInOrder, script) {
|
|
212
|
+
for (const dir of dirsInOrder) {
|
|
213
|
+
console.log(`\u25B6 ${dir}: bun run ${script}`);
|
|
214
|
+
const p = Bun.spawn(["bun", "run", script], {
|
|
215
|
+
cwd: dir,
|
|
216
|
+
stdout: "inherit",
|
|
217
|
+
stderr: "inherit"
|
|
218
|
+
});
|
|
219
|
+
const code = await p.exited;
|
|
220
|
+
if (code !== 0) {
|
|
221
|
+
throw new Error(`Failed in ${dir} with exit code ${code}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async function runTierParallel(dirs, script, concurrency) {
|
|
226
|
+
if (dirs.length === 0) return;
|
|
227
|
+
let i = 0;
|
|
228
|
+
const errs = [];
|
|
229
|
+
async function worker() {
|
|
230
|
+
while (true) {
|
|
231
|
+
const idx = i++;
|
|
232
|
+
if (idx >= dirs.length) break;
|
|
233
|
+
const dir = dirs[idx];
|
|
234
|
+
if (!dir) continue;
|
|
235
|
+
console.log(`\u25B6 ${dir}: bun run ${script}`);
|
|
236
|
+
const p = Bun.spawn(["bun", "run", script], {
|
|
237
|
+
cwd: dir,
|
|
238
|
+
stdout: "inherit",
|
|
239
|
+
stderr: "inherit"
|
|
240
|
+
});
|
|
241
|
+
const code = await p.exited;
|
|
242
|
+
if (code !== 0) {
|
|
243
|
+
errs.push(new Error(`Failed in ${dir} with exit code ${code}`));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const n = Math.max(1, Math.min(concurrency, dirs.length));
|
|
248
|
+
await Promise.all(Array.from({ length: n }, () => worker()));
|
|
249
|
+
if (errs.length > 0) {
|
|
250
|
+
throw errs[0];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function main() {
|
|
254
|
+
const opts = parseArgs();
|
|
255
|
+
const rootPkg = readJSON(join(cwd, "package.json"));
|
|
256
|
+
const wsDirs = getWorkspaceDirs(rootPkg);
|
|
257
|
+
if (wsDirs.length === 0) {
|
|
258
|
+
console.error("No workspaces found in package.json");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
const pkgs = wsDirs.map((dir) => {
|
|
262
|
+
try {
|
|
263
|
+
const json = readJSON(join(dir, "package.json"));
|
|
264
|
+
return {
|
|
265
|
+
name: json.name,
|
|
266
|
+
dir,
|
|
267
|
+
scripts: json.scripts ?? {},
|
|
268
|
+
json
|
|
269
|
+
};
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.warn(
|
|
272
|
+
`Failed to load package.json in ${dir}:`,
|
|
273
|
+
error instanceof Error ? error.message : error
|
|
274
|
+
);
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}).filter((p) => p !== null && p.name.length > 0);
|
|
278
|
+
const selected = pkgs.filter((p) => {
|
|
279
|
+
const matchOnly = opts.only.length > 0 ? opts.only.some((g) => matchGlob(p.name, g) || matchGlob(p.dir, g)) : true;
|
|
280
|
+
const matchExclude = opts.exclude.length > 0 ? opts.exclude.some((g) => matchGlob(p.name, g) || matchGlob(p.dir, g)) : false;
|
|
281
|
+
return matchOnly && !matchExclude;
|
|
282
|
+
});
|
|
283
|
+
if (selected.length === 0) {
|
|
284
|
+
console.error("No packages matched selection.");
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
const names = new Set(selected.map((p) => p.name));
|
|
288
|
+
const edges = /* @__PURE__ */ new Map();
|
|
289
|
+
const ensure = (k) => {
|
|
290
|
+
const existing = edges.get(k);
|
|
291
|
+
if (existing) return existing;
|
|
292
|
+
const newSet = /* @__PURE__ */ new Set();
|
|
293
|
+
edges.set(k, newSet);
|
|
294
|
+
return newSet;
|
|
295
|
+
};
|
|
296
|
+
for (const p of selected) {
|
|
297
|
+
const all = collectDeps(p.json, opts.includeDev, opts.includePeer);
|
|
298
|
+
for (const depName of Object.keys(all)) {
|
|
299
|
+
if (!names.has(depName)) continue;
|
|
300
|
+
ensure(depName).add(p.name);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const nodes = selected.map((p) => p.name);
|
|
304
|
+
const tiers = topoTiers(nodes, edges);
|
|
305
|
+
const hasScript = (name) => {
|
|
306
|
+
const pkg = selected.find((p) => p.name === name);
|
|
307
|
+
if (!pkg) {
|
|
308
|
+
console.warn(`Package not found: ${name}`);
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
return Boolean(pkg.scripts[opts.script]);
|
|
312
|
+
};
|
|
313
|
+
if (opts.debug) {
|
|
314
|
+
console.log(`Script: ${opts.script}`);
|
|
315
|
+
console.log(
|
|
316
|
+
`Mode: ${opts.sequential ? "sequential" : `parallel by tier (jobs=${opts.jobs})`}`
|
|
317
|
+
);
|
|
318
|
+
console.log(
|
|
319
|
+
`Edges: deps + optional${opts.includeDev ? " + dev" : ""}${opts.includePeer ? " + peer" : ""}`
|
|
320
|
+
);
|
|
321
|
+
console.log("");
|
|
322
|
+
}
|
|
323
|
+
const tiersDirs = tiers.map(
|
|
324
|
+
(tier) => tier.filter(hasScript).map((name) => {
|
|
325
|
+
const pkg = selected.find((p) => p.name === name);
|
|
326
|
+
if (!pkg) {
|
|
327
|
+
throw new Error(`Package not found: ${name}`);
|
|
328
|
+
}
|
|
329
|
+
return pkg.dir;
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
const seqDirs = tiers.flat().filter(hasScript).map((name) => {
|
|
333
|
+
const pkg = selected.find((p) => p.name === name);
|
|
334
|
+
if (!pkg) {
|
|
335
|
+
throw new Error(`Package not found: ${name}`);
|
|
336
|
+
}
|
|
337
|
+
return pkg.dir;
|
|
338
|
+
});
|
|
339
|
+
if (opts.debug) {
|
|
340
|
+
if (opts.sequential) {
|
|
341
|
+
console.log("Plan (sequential order):");
|
|
342
|
+
seqDirs.forEach((d, i) => console.log(` ${i + 1}. ${d}`));
|
|
343
|
+
console.log("");
|
|
344
|
+
console.log("Command preview:");
|
|
345
|
+
console.log(
|
|
346
|
+
seqDirs.map((d) => `(cd ${JSON.stringify(d)} && bun run ${opts.script})`).join(" && ")
|
|
347
|
+
);
|
|
348
|
+
} else {
|
|
349
|
+
console.log("Plan (tiers):");
|
|
350
|
+
tiersDirs.forEach((dirs, i) => {
|
|
351
|
+
if (!dirs.length) return;
|
|
352
|
+
console.log(` Tier ${i + 1}:`);
|
|
353
|
+
dirs.forEach((d) => console.log(` - ${d}`));
|
|
354
|
+
});
|
|
355
|
+
console.log("");
|
|
356
|
+
console.log("Command preview (conceptual):");
|
|
357
|
+
console.log(
|
|
358
|
+
tiersDirs.filter((dirs) => dirs.length).map(
|
|
359
|
+
(dirs) => dirs.map(
|
|
360
|
+
(d) => `(cd ${JSON.stringify(d)} && bun run ${opts.script})`
|
|
361
|
+
).join(" & ") + " && wait"
|
|
362
|
+
).join(" && ")
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (opts.printOnly) return;
|
|
367
|
+
if (opts.sequential) {
|
|
368
|
+
await runSequential(seqDirs, opts.script);
|
|
369
|
+
} else {
|
|
370
|
+
for (const dirs of tiersDirs) {
|
|
371
|
+
if (dirs.length === 0) continue;
|
|
372
|
+
await runTierParallel(dirs, opts.script, opts.jobs);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
main().catch((err) => {
|
|
377
|
+
throw err;
|
|
378
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forklaunch/bunrun",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Bun-native TypeScript workspace script runner with topological ordering",
|
|
5
|
+
"homepage": "https://github.com/forklaunch/forklaunch-js#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/forklaunch/forklaunch-js/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/forklaunch/forklaunch-js.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Rohin Bhargava",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"bunrun": "dist/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"bun": ">=1.1.0"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"keywords": [
|
|
26
|
+
"bun",
|
|
27
|
+
"workspace",
|
|
28
|
+
"monorepo",
|
|
29
|
+
"build",
|
|
30
|
+
"topological",
|
|
31
|
+
"typescript",
|
|
32
|
+
"cli"
|
|
33
|
+
],
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@typescript/native-preview": "7.0.0-dev.20250911.1",
|
|
36
|
+
"tsup": "^8.0.0",
|
|
37
|
+
"typescript": "^5.6.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsgo --noEmit && tsup",
|
|
41
|
+
"check": "depcheck",
|
|
42
|
+
"clean": "rm -rf dist pnpm.lock.yaml node_modules",
|
|
43
|
+
"docs": "typedoc --out docs *",
|
|
44
|
+
"format": "prettier --ignore-path=.prettierignore --config .prettierrc '**/*.{ts,tsx,json}' --write",
|
|
45
|
+
"lint": "eslint . -c eslint.config.mjs",
|
|
46
|
+
"lint:fix": "eslint . -c eslint.config.mjs --fix",
|
|
47
|
+
"publish:package": "./publish-package.bash",
|
|
48
|
+
"test": "vitest --passWithNoTests"
|
|
49
|
+
}
|
|
50
|
+
}
|