@omni-oss/task-bench 0.0.0-beta.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.
Files changed (73) hide show
  1. package/README.md +328 -0
  2. package/dist/bench/index.d.ts +82 -0
  3. package/dist/bench/index.d.ts.map +1 -0
  4. package/dist/bench/install.d.ts +5 -0
  5. package/dist/bench/install.d.ts.map +1 -0
  6. package/dist/bench/report.d.ts +9 -0
  7. package/dist/bench/report.d.ts.map +1 -0
  8. package/dist/bench/stats.d.ts +12 -0
  9. package/dist/bench/stats.d.ts.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/config.d.ts +91 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/generate/index.d.ts +19 -0
  15. package/dist/generate/index.d.ts.map +1 -0
  16. package/dist/generate/templates.d.ts +20 -0
  17. package/dist/generate/templates.d.ts.map +1 -0
  18. package/dist/graph.d.ts +21 -0
  19. package/dist/graph.d.ts.map +1 -0
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.mjs +2 -0
  23. package/dist/src-D3XyMXAu.mjs +1167 -0
  24. package/dist/suite/index.d.ts +49 -0
  25. package/dist/suite/index.d.ts.map +1 -0
  26. package/dist/suite/preset.d.ts +93 -0
  27. package/dist/suite/preset.d.ts.map +1 -0
  28. package/dist/suite/report.d.ts +7 -0
  29. package/dist/suite/report.d.ts.map +1 -0
  30. package/dist/task-bench-cli.mjs +107 -0
  31. package/dist/tools/index.d.ts +18 -0
  32. package/dist/tools/index.d.ts.map +1 -0
  33. package/dist/tools/moon.d.ts +10 -0
  34. package/dist/tools/moon.d.ts.map +1 -0
  35. package/dist/tools/nx.d.ts +7 -0
  36. package/dist/tools/nx.d.ts.map +1 -0
  37. package/dist/tools/omni.d.ts +7 -0
  38. package/dist/tools/omni.d.ts.map +1 -0
  39. package/dist/tools/turbo.d.ts +5 -0
  40. package/dist/tools/turbo.d.ts.map +1 -0
  41. package/dist/tools/types.d.ts +77 -0
  42. package/dist/tools/types.d.ts.map +1 -0
  43. package/package.json +41 -0
  44. package/project.omni.yaml +33 -0
  45. package/src/bench/index.ts +323 -0
  46. package/src/bench/install.ts +12 -0
  47. package/src/bench/report.ts +142 -0
  48. package/src/bench/stats.spec.ts +35 -0
  49. package/src/bench/stats.ts +38 -0
  50. package/src/cli/index.ts +410 -0
  51. package/src/config.ts +138 -0
  52. package/src/generate/index.ts +215 -0
  53. package/src/generate/templates.ts +87 -0
  54. package/src/graph.spec.ts +119 -0
  55. package/src/graph.ts +120 -0
  56. package/src/index.ts +31 -0
  57. package/src/suite/index.ts +113 -0
  58. package/src/suite/preset.spec.ts +95 -0
  59. package/src/suite/preset.ts +253 -0
  60. package/src/suite/report.ts +135 -0
  61. package/src/tools/adapters.spec.ts +95 -0
  62. package/src/tools/config.spec.ts +73 -0
  63. package/src/tools/index.ts +76 -0
  64. package/src/tools/moon.ts +106 -0
  65. package/src/tools/nx.ts +106 -0
  66. package/src/tools/omni.ts +96 -0
  67. package/src/tools/turbo.ts +78 -0
  68. package/src/tools/types.ts +116 -0
  69. package/tsconfig.json +4 -0
  70. package/tsconfig.project.json +6 -0
  71. package/tsconfig.types.json +4 -0
  72. package/vite.config.ts +29 -0
  73. package/vitest.config.unit.ts +13 -0
package/README.md ADDED
@@ -0,0 +1,328 @@
1
+ # @omni-oss/task-bench
2
+
3
+ Generate configurable, minimal test workspaces and benchmark **task-execution
4
+ overhead** across [omni](https://github.com/omni-oss/omni),
5
+ [Turborepo](https://turbo.build), [Nx](https://nx.dev), and
6
+ [moonrepo](https://moonrepo.dev).
7
+
8
+ The goal is to isolate the cost of **project discovery** and **task caching**
9
+ rather than the tasks themselves. Every generated task is fast, deterministic,
10
+ and produces real output (logs + files) so each runner's cache and log-capture
11
+ machinery is exercised. The four runners are configured as equivalently as
12
+ possible from a single source of truth.
13
+
14
+ ## How it works
15
+
16
+ For a given config the harness generates one workspace on disk containing:
17
+
18
+ - `packages/<prefix><n>/` — minimal JS projects, each with:
19
+ - `package.json` (scripts + `workspace:*` deps encoding the project graph),
20
+ - `src/index.js` (a cache input),
21
+ - `task.mjs` (the shared, deterministic task runner),
22
+ - `project.omni.yaml` (omni), `project.json` (nx), and `moon.yml` (moon).
23
+ - Root `workspace.omni.yaml`, `turbo.json`, `nx.json`, and `.moon/*.yml`
24
+ describing the same task graph (`tN` depends on `t(N-1)` and/or `^tN`) with
25
+ identical inputs (`package.json`, `task.mjs`, `src/**`) and outputs
26
+ (`dist/tN.*`). The workspace is also `git init`-ed and committed, which moon
27
+ requires to enable its cache (and is harmless/realistic for the others).
28
+
29
+ It then benchmarks each enabled tool in two scenarios:
30
+
31
+ - **cold** — caches + outputs wiped before each run (discovery + full exec).
32
+ - **warm** — caches primed, so every task should be a cache hit. This is the
33
+ key metric for discovery + cache-restore overhead.
34
+
35
+ ### Fairness & correctness
36
+
37
+ - **Verified cache hits.** Each task appends one line to an out-of-tree log
38
+ *only when it actually executes* (cache hits skip the process entirely).
39
+ The harness counts these to report a real, tool-agnostic cache-hit rate per
40
+ run, so "warm == all cached" is verified rather than assumed. A warm run that
41
+ is not 100% cached is flagged in the report. (Turbo's strict env mode is
42
+ handled via `globalPassThroughEnv` so the marker survives without affecting
43
+ cache keys.)
44
+ - **Concurrency parity.** The same max-parallelism is passed to every runner
45
+ (`omni -c`, `turbo --concurrency`, `nx --parallel`); defaults to the CPU
46
+ count. Use `--concurrency` to pin it.
47
+ - **Daemon handling.** By default each tool is allowed to use its persistent
48
+ daemon (Turbo's `turbod`, the Nx daemon) so warm runs reflect each tool's
49
+ best incremental performance; the daemon is warmed by an unmeasured prime run
50
+ and left alive across warm runs. Cold runs deliberately tear the daemon down
51
+ (and clear caches) so they include cold-start cost. Daemons are always
52
+ stopped as cleanup afterwards so nothing leaks. Pass `--no-daemon` to disable
53
+ daemons entirely (Turbo `--no-daemon`, `NX_DAEMON=false`); omni and moon have
54
+ no daemon.
55
+ - **Statistics.** Reports median ± standard deviation; full per-run samples
56
+ (duration, exit code, executed-task count) are written to the JSON output.
57
+
58
+ ## CLI
59
+
60
+ ```sh
61
+ # Generate a workspace only.
62
+ task-bench generate -o /tmp/bench --projects 200 --tasks 4 --strategy layered
63
+
64
+ # Generate, `bun install`, benchmark, and print a report.
65
+ task-bench bench -o /tmp/bench --projects 200 --tasks 4 \
66
+ --strategy random --edge-probability 0.3 \
67
+ --cold-runs 3 --warm-runs 5 --json results.json
68
+
69
+ # Benchmark an already-generated workspace.
70
+ task-bench run -d /tmp/bench --tools omni,turbo --task t3
71
+
72
+ # Run a whole preset of scenarios and summarize (see "Comprehensive suite").
73
+ task-bench suite --preset full --json suite.json --md suite.md
74
+
75
+ # Resolve a config and print it (no side effects).
76
+ task-bench inspect --projects 50 --strategy chain
77
+ ```
78
+
79
+ ### Key options
80
+
81
+ | Option | Description |
82
+ | --- | --- |
83
+ | `-o, --out <dir>` | Root dir to generate the workspace into. |
84
+ | `--projects <n>` | Number of projects. |
85
+ | `--tasks <n>` | Tasks per project (`t0..tN-1`). |
86
+ | `--strategy <s>` | `isolated`, `chain`, `fan-out`, `layered`, or `random`. |
87
+ | `--layers <n>` | Layers for the `layered` strategy. |
88
+ | `--fanout <n>` | Max upstream deps per project. |
89
+ | `--edge-probability <p>` | Edge probability for the `random` strategy. |
90
+ | `--log-lines <n>` | Log lines printed per task. |
91
+ | `--work <n>` | CPU work iterations per task. |
92
+ | `--output-files <n>` | Output files written per task. |
93
+ | `--tools <list>` | Comma-separated `omni,turbo,nx,moon`. |
94
+ | `--turbo-version` / `--nx-version` / `--moon-version` / `--bun-version` | Pin the version of each tool to install. |
95
+ | `--concurrency <n>` | Max parallel tasks, applied identically to every runner (default: CPU count). |
96
+ | `--no-daemon` | Disable each tool's persistent daemon (Turbo, Nx). |
97
+ | `--no-chain` / `--no-fan-upstream` | Disable intra/inter-project task deps. |
98
+ | `--config <file>` | JSON config to use as a base for overrides. |
99
+
100
+ ## Library
101
+
102
+ ```ts
103
+ import { generateWorkspace, runBenchmark, formatReport } from "@omni-oss/task-bench";
104
+
105
+ await generateWorkspace("/tmp/bench", {
106
+ projects: 100,
107
+ tasksPerProject: 3,
108
+ dependency: { strategy: "layered", layers: 6, fanout: 3 },
109
+ });
110
+
111
+ const result = await runBenchmark("/tmp/bench", { warmRuns: 5 });
112
+ console.log(formatReport(result));
113
+ ```
114
+
115
+ ## Comprehensive benchmark suite
116
+
117
+ The `suite` command runs a **preset of scenarios** end-to-end (generate →
118
+ install → benchmark each) and summarizes them into a combined report, with
119
+ `--json` and/or `--md` output.
120
+
121
+ ```sh
122
+ # List the built-in presets.
123
+ task-bench suite --list
124
+ # quick | shapes | scale | density | daemon | full
125
+
126
+ # Run a built-in preset and write both machine- and human-readable reports.
127
+ task-bench suite --preset full --json suite.json --md suite.md
128
+
129
+ # Override run parameters for every scenario in the preset.
130
+ task-bench suite --preset scale --cold-runs 3 --warm-runs 5 --concurrency 8
131
+
132
+ # Run only two tools, and keep the generated workspaces around.
133
+ task-bench suite --preset shapes --tools omni,turbo --keep -o /tmp/suite
134
+
135
+ # Run a custom scenario matrix from a JSON file.
136
+ task-bench suite --file my-suite.json --md report.md
137
+ ```
138
+
139
+ Built-in presets: **`quick`** (tiny smoke test), **`shapes`** (dependency-graph
140
+ shapes at fixed scale), **`scale`** (50–600 projects), **`density`** (tasks per
141
+ project), **`daemon`** (daemon on vs off), and **`full`** (shapes + scale +
142
+ density). A custom preset file looks like:
143
+
144
+ ```jsonc
145
+ {
146
+ "name": "my sweep",
147
+ "defaults": {
148
+ "config": { "tasksPerProject": 3, "dependency": { "strategy": "layered" } },
149
+ "run": { "concurrency": 8, "coldRuns": 2, "warmRuns": 3 }
150
+ },
151
+ "scenarios": [
152
+ { "name": "small", "config": { "projects": 50 } },
153
+ { "name": "large", "config": { "projects": 500 } },
154
+ { "name": "large-nodaemon", "config": { "projects": 500 }, "run": { "daemon": false } }
155
+ ]
156
+ }
157
+ ```
158
+
159
+ The Markdown report contains two summary matrices (warm + cold median per tool
160
+ per scenario) followed by the detailed per-scenario tables; the JSON contains
161
+ the full resolved config, run options, and per-run samples for every scenario.
162
+
163
+ ### Scripting it yourself
164
+
165
+ If you prefer explicit shell control, the equivalent manual loop is:
166
+
167
+ ````bash
168
+ #!/usr/bin/env bash
169
+ set -euo pipefail
170
+
171
+ # Use the built CLI (`task-bench`) or the dev entry as below.
172
+ BENCH="bun scripts/task-bench/src/cli/index.ts"
173
+ OUT=/tmp/task-bench-suite
174
+ RESULTS="$OUT/results"
175
+ SUITE="$OUT/SUITE.md"
176
+ mkdir -p "$RESULTS"
177
+ : > "$SUITE"
178
+
179
+ run() { # run <label> <bench|run> <args...>
180
+ local label="$1"; shift
181
+ echo "### $label" | tee -a "$SUITE"
182
+ echo '```' >> "$SUITE"
183
+ $BENCH "$@" --json "$RESULTS/$label.json" 2>/dev/null | tee -a "$SUITE"
184
+ echo '```' >> "$SUITE"
185
+ }
186
+
187
+ # 1) Dependency-shape sweep (fixed scale): how graph shape affects overhead.
188
+ for strat in isolated chain fan-out layered random; do
189
+ run "shape-$strat" bench -o "$OUT/shape-$strat" \
190
+ --projects 120 --tasks 3 --strategy "$strat" \
191
+ --concurrency 8 --cold-runs 2 --warm-runs 3
192
+ done
193
+
194
+ # 2) Scale sweep (layered graph): how overhead grows with graph size.
195
+ for n in 50 150 300 600; do
196
+ run "scale-$n" bench -o "$OUT/scale-$n" \
197
+ --projects "$n" --tasks 3 --strategy layered --layers 8 \
198
+ --concurrency 8 --cold-runs 2 --warm-runs 3
199
+ done
200
+
201
+ # 3) Task-density sweep (fixed project count).
202
+ for t in 2 5 10; do
203
+ run "density-$t" bench -o "$OUT/density-$t" \
204
+ --projects 120 --tasks "$t" --strategy layered \
205
+ --concurrency 8 --cold-runs 2 --warm-runs 3
206
+ done
207
+
208
+ # 4) Daemon on vs off (reuse one installed workspace).
209
+ run "daemon-on" bench -o "$OUT/daemon" --projects 200 --tasks 3 \
210
+ --strategy layered --concurrency 8 --cold-runs 2 --warm-runs 3
211
+ run "daemon-off" run -d "$OUT/daemon" --no-daemon \
212
+ --concurrency 8 --cold-runs 2 --warm-runs 3
213
+
214
+ echo "Suite written to $SUITE ; per-scenario JSON in $RESULTS"
215
+ # rm -rf "$OUT" # uncomment to clean up workspaces afterwards
216
+ ````
217
+
218
+ Each JSON result contains per-run samples (`durationMs`, `exitCode`,
219
+ `executed`), the verified `taskGraphSize`, and `stats` (min/max/mean/median/
220
+ stddev) for both scenarios, so you can post-process or chart them however you
221
+ like.
222
+
223
+ ### Sample results
224
+
225
+ Collected on one machine (Linux, 8-way concurrency, 3 tasks/project, layered
226
+ unless noted; `warm` = verified 100% cache hit, the discovery + cache-restore
227
+ overhead metric). Absolute numbers are hardware-dependent — read the *ratios*.
228
+
229
+ **Four-runner snapshot** (40 projects × 3 tasks, 120 nodes, layered):
230
+
231
+ | tool | warm | cold |
232
+ | --- | --- | --- |
233
+ | omni | **76ms** | 990ms |
234
+ | turbo | 135ms | 915ms |
235
+ | nx | 434ms | 2.27s |
236
+ | moon | 448ms | 1.45s |
237
+
238
+ The larger sweeps below predate the moon addition (omni/turbo/nx only); rerun
239
+ `task-bench suite --preset full` to regenerate them with all four runners.
240
+
241
+ <!-- SUITE_RESULTS -->
242
+
243
+ **Dependency-shape sweep** (120 projects × 3 tasks, 360 task-graph nodes):
244
+
245
+ | strategy | omni warm | turbo warm | nx warm | omni cold | turbo cold | nx cold |
246
+ | --- | --- | --- | --- | --- | --- | --- |
247
+ | isolated | 292ms | **239ms** | 644ms | 3.27s | 2.52s | 8.22s |
248
+ | chain | 403ms | **231ms** | 1.09s | 5.36s | 4.91s | 9.09s |
249
+ | fan-out | 304ms | **246ms** | 678ms | 3.46s | 2.61s | 8.79s |
250
+ | layered | 252ms | **246ms** | 678ms | 3.21s | 2.63s | 8.71s |
251
+ | random | 284ms | **254ms** | 797ms | 3.59s | 3.12s | 8.72s |
252
+
253
+ **Scale sweep** (layered, 3 tasks):
254
+
255
+ | projects (nodes) | omni warm | turbo warm | nx warm | omni cold | turbo cold | nx cold |
256
+ | --- | --- | --- | --- | --- | --- | --- |
257
+ | 50 (150) | **106ms** | 134ms | 478ms | 1.34s | 1.14s | 2.81s |
258
+ | 150 (450) | 295ms | **287ms** | 764ms | 4.05s | 3.25s | 12.23s |
259
+ | 300 (900) | 715ms | **523ms** | 1.17s | 8.62s | 6.44s | 38.68s |
260
+
261
+ **Daemon on vs off** (200 projects × 3 tasks, 600 nodes):
262
+
263
+ | mode | omni warm | turbo warm | nx warm | omni cold | turbo cold | nx cold |
264
+ | --- | --- | --- | --- | --- | --- | --- |
265
+ | daemon on | 454ms | **361ms** | 915ms | 5.60s | 4.31s | 19.50s |
266
+ | daemon off | 455ms | **359ms** | 924ms | 5.62s | 4.32s | 15.27s |
267
+
268
+ **Observations**
269
+
270
+ - **Warm overhead:** omni and turbo are close (turbo pulls slightly ahead as
271
+ scale grows); nx is consistently **~2.3–4.7× slower** on warm cache hits. At
272
+ small scale (50 projects) omni is fastest; from ~120 projects up turbo leads.
273
+ - **Graph shape matters most for cold:** the `chain` strategy (deep, low
274
+ parallelism) roughly doubles cold time for omni/turbo and gives nx its worst
275
+ warm number, while `isolated`/`layered` (wide, parallel) are cheapest.
276
+ - **Scale:** warm overhead grows roughly linearly with task-graph size for all
277
+ three; nx **cold** scales worst (38.7s at 300 projects vs 6–9s for the
278
+ others).
279
+ - **Daemons:** on/off barely moves omni/turbo (omni has none; turbo warm is
280
+ unchanged) and nx warm is essentially flat — but nx **cold is faster with the
281
+ daemon off** (~15s vs ~19s) because daemon-mode cold pays a teardown/restart
282
+ each run. So nx's daemon does not materially reduce the per-invocation warm
283
+ overhead this benchmark isolates.
284
+
285
+ > Reproduce with the script above; exact numbers vary by machine, the relative
286
+ > ratios are the takeaway.
287
+
288
+ ## Tool adapters
289
+
290
+ Each runner is a self-contained adapter (`src/tools/<tool>.ts`) that owns
291
+ *everything* tool-specific, so the generator and the other tools stay
292
+ decoupled. An adapter declares:
293
+
294
+ - `supportedVersions` — semver ranges it supports. The version to install is
295
+ configurable (`versions.<tool>` / `--<tool>-version`); if it falls outside
296
+ the supported ranges the harness fails fast with a clear error. External
297
+ tools (omni) instead implement `detectVersion()` and validate the installed
298
+ binary.
299
+ - `devDependencies(config)` — the npm packages it needs in the root
300
+ `package.json`.
301
+ - `setup(ctx)` — derives and writes its own config from the generated
302
+ `projects` (e.g. `turbo.json`, `nx.json` + `project.json`, `.moon/*.yml` +
303
+ `moon.yml`, `workspace.omni.yaml` + `project.omni.yaml`).
304
+ - `run` / `env` / `clearCaches` / `stopDaemon` — the runtime behavior.
305
+
306
+ The generator only writes the neutral workspace (projects, `src/index.js`,
307
+ `task.mjs`), then hands the project list to each enabled adapter. Adding a new
308
+ runner is a single new file plus registering it in `src/tools/index.ts`.
309
+
310
+ ## Notes
311
+
312
+ - The dependency graph is deterministic for a given `seed`.
313
+ - Generated workspaces are self-contained and safe to place anywhere outside
314
+ this repository (each has its own `workspace.omni.yaml`).
315
+
316
+ ### Known limitations
317
+
318
+ - Numbers are wall-clock process timings on a single machine; absolute values
319
+ vary by hardware, but the harness isolates *relative* discovery/caching
320
+ overhead.
321
+ - Cold runs are fully fresh per tool (caches cleared; daemons torn down in
322
+ daemon mode), so cold includes each tool's start-up cost. Warm runs are the
323
+ primary overhead metric.
324
+ - Task output is captured (piped) for all runners equally so every tool's
325
+ log-capture path is exercised.
326
+ - moon only enables its cache inside a git repository, so generated workspaces
327
+ are committed automatically; benchmarking an existing workspace assumes it is
328
+ already a git repo.
@@ -0,0 +1,82 @@
1
+ import { Tool } from '../config';
2
+ import { Stats } from './stats';
3
+ export interface RunSample {
4
+ durationMs: number;
5
+ exitCode: number;
6
+ stdout: string;
7
+ stderr: string;
8
+ /** Number of tasks that actually executed (0 == a full cache hit). */
9
+ executed: number;
10
+ ok: boolean;
11
+ }
12
+ export interface ScenarioResult {
13
+ runs: number;
14
+ failures: number;
15
+ stats: Stats;
16
+ /** Median number of tasks that actually executed across the runs. */
17
+ executedMedian: number;
18
+ }
19
+ export interface ToolResult {
20
+ tool: Tool;
21
+ task: string;
22
+ /** Size of the executed task graph (tasks run on a cold, uncached run). */
23
+ taskGraphSize: number;
24
+ cold: ScenarioResult;
25
+ warm: ScenarioResult;
26
+ error?: string;
27
+ }
28
+ export interface BenchmarkResult {
29
+ rootDir: string;
30
+ task: string;
31
+ projects: number;
32
+ tasksPerProject: number;
33
+ concurrency: number;
34
+ daemon: boolean;
35
+ /** Resolved version of each benchmarked tool (detected for omni). */
36
+ versions: Record<string, string | null>;
37
+ generatedAt: string;
38
+ tools: ToolResult[];
39
+ }
40
+ export type BenchEvent = {
41
+ kind: "tool-start";
42
+ tool: Tool;
43
+ } | {
44
+ kind: "scenario";
45
+ tool: Tool;
46
+ scenario: "cold" | "warm";
47
+ run: number;
48
+ total: number;
49
+ sample: RunSample;
50
+ } | {
51
+ kind: "tool-error";
52
+ tool: Tool;
53
+ error: string;
54
+ } | {
55
+ kind: "tool-unsuccessful";
56
+ tool: Tool;
57
+ sample: RunSample;
58
+ };
59
+ export interface RunBenchmarkOptions {
60
+ tools?: Tool[] | undefined;
61
+ task?: string | undefined;
62
+ coldRuns?: number | undefined;
63
+ warmRuns?: number | undefined;
64
+ concurrency?: number | undefined;
65
+ /** Allow each tool's persistent daemon (default true). */
66
+ daemon?: boolean | undefined;
67
+ onEvent?: ((event: BenchEvent) => void) | undefined;
68
+ }
69
+ /**
70
+ * Benchmark the enabled tools against an already-generated workspace.
71
+ *
72
+ * For each tool we measure two scenarios:
73
+ * - cold: caches + outputs wiped before every run (discovery + full exec + cache write)
74
+ * - warm: caches primed, so ideally every task is a cache hit
75
+ * (isolates discovery + cache-restore overhead)
76
+ *
77
+ * A tool-agnostic execution counter (see the generated task runner) records how
78
+ * many tasks actually ran, so warm-run cache effectiveness is *verified* rather
79
+ * than assumed. Concurrency is pinned identically across all runners.
80
+ */
81
+ export declare function runBenchmark(rootDir: string, options?: RunBenchmarkOptions): Promise<BenchmarkResult>;
82
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bench/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAuB,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAG3D,OAAO,EAAgB,KAAK,KAAK,EAAE,MAAM,SAAS,CAAC;AAEnD,MAAM,WAAW,SAAS;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,cAAc,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,UAAU,GAChB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,IAAI,CAAA;CAAE,GAClC;IACI,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,SAAS,CAAC;CACrB,GACD;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjD;IACI,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,SAAS,CAAC;CACrB,CAAC;AAER,MAAM,WAAW,mBAAmB;IAChC,KAAK,CAAC,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;CACvD;AAuED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAC9B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAClC,OAAO,CAAC,eAAe,CAAC,CA8J1B"}
@@ -0,0 +1,5 @@
1
+ /** Install dependencies in a generated workspace with bun. */
2
+ export declare function installWorkspace(dir: string, opts?: {
3
+ quiet?: boolean;
4
+ }): Promise<void>;
5
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/bench/install.ts"],"names":[],"mappings":"AAEA,8DAA8D;AAC9D,wBAAsB,gBAAgB,CAClC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC/B,OAAO,CAAC,IAAI,CAAC,CAKf"}
@@ -0,0 +1,9 @@
1
+ import { BenchmarkResult } from './index';
2
+ /**
3
+ * Render a human-readable comparison table plus a short takeaway. The warm
4
+ * column is the key metric: with all tasks cached it approximates each tool's
5
+ * discovery + cache-restore overhead. The warm-cache-hit column verifies that
6
+ * assumption held (should be 100%).
7
+ */
8
+ export declare function formatReport(result: BenchmarkResult): string;
9
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/bench/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAc,MAAM,SAAS,CAAC;AA6B3D;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAkG5D"}
@@ -0,0 +1,12 @@
1
+ /** Summary statistics for a set of timing samples (all in milliseconds). */
2
+ export interface Stats {
3
+ samples: number[];
4
+ min: number;
5
+ max: number;
6
+ mean: number;
7
+ median: number;
8
+ stddev: number;
9
+ }
10
+ export declare function computeStats(samples: number[]): Stats;
11
+ export declare function formatMs(ms: number): string;
12
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../../src/bench/stats.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,WAAW,KAAK;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,KAAK,CAsBrD;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAG3C"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,91 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * The supported inter-project dependency graph shapes. These control how much
4
+ * of a task graph a runner has to walk during discovery / scheduling, which is
5
+ * what we are trying to measure.
6
+ */
7
+ export declare const DEPENDENCY_STRATEGIES: readonly ["isolated", "chain", "fan-out", "layered", "random"];
8
+ export declare const DependencyStrategySchema: z.ZodEnum<{
9
+ isolated: "isolated";
10
+ chain: "chain";
11
+ "fan-out": "fan-out";
12
+ layered: "layered";
13
+ random: "random";
14
+ }>;
15
+ export type DependencyStrategy = z.infer<typeof DependencyStrategySchema>;
16
+ /** The task runners we know how to generate configuration for and benchmark. */
17
+ export declare const TOOLS: readonly ["omni", "turbo", "nx", "moon"];
18
+ export declare const ToolSchema: z.ZodEnum<{
19
+ omni: "omni";
20
+ turbo: "turbo";
21
+ nx: "nx";
22
+ moon: "moon";
23
+ }>;
24
+ export type Tool = z.infer<typeof ToolSchema>;
25
+ export declare const DependencyConfigSchema: z.ZodPrefault<z.ZodObject<{
26
+ strategy: z.ZodDefault<z.ZodEnum<{
27
+ isolated: "isolated";
28
+ chain: "chain";
29
+ "fan-out": "fan-out";
30
+ layered: "layered";
31
+ random: "random";
32
+ }>>;
33
+ layers: z.ZodDefault<z.ZodNumber>;
34
+ fanout: z.ZodDefault<z.ZodNumber>;
35
+ edgeProbability: z.ZodDefault<z.ZodNumber>;
36
+ }, z.core.$strip>>;
37
+ export declare const TaskConfigSchema: z.ZodPrefault<z.ZodObject<{
38
+ logLines: z.ZodDefault<z.ZodNumber>;
39
+ workIterations: z.ZodDefault<z.ZodNumber>;
40
+ outputFiles: z.ZodDefault<z.ZodNumber>;
41
+ chainWithinProject: z.ZodDefault<z.ZodBoolean>;
42
+ fanUpstream: z.ZodDefault<z.ZodBoolean>;
43
+ }, z.core.$strip>>;
44
+ export declare const VersionsConfigSchema: z.ZodPrefault<z.ZodObject<{
45
+ turbo: z.ZodDefault<z.ZodString>;
46
+ nx: z.ZodDefault<z.ZodString>;
47
+ moon: z.ZodDefault<z.ZodString>;
48
+ bun: z.ZodDefault<z.ZodString>;
49
+ }, z.core.$strip>>;
50
+ export declare const HarnessConfigSchema: z.ZodObject<{
51
+ seed: z.ZodDefault<z.ZodNumber>;
52
+ projectPrefix: z.ZodDefault<z.ZodString>;
53
+ projects: z.ZodDefault<z.ZodNumber>;
54
+ tasksPerProject: z.ZodDefault<z.ZodNumber>;
55
+ dependency: z.ZodPrefault<z.ZodObject<{
56
+ strategy: z.ZodDefault<z.ZodEnum<{
57
+ isolated: "isolated";
58
+ chain: "chain";
59
+ "fan-out": "fan-out";
60
+ layered: "layered";
61
+ random: "random";
62
+ }>>;
63
+ layers: z.ZodDefault<z.ZodNumber>;
64
+ fanout: z.ZodDefault<z.ZodNumber>;
65
+ edgeProbability: z.ZodDefault<z.ZodNumber>;
66
+ }, z.core.$strip>>;
67
+ task: z.ZodPrefault<z.ZodObject<{
68
+ logLines: z.ZodDefault<z.ZodNumber>;
69
+ workIterations: z.ZodDefault<z.ZodNumber>;
70
+ outputFiles: z.ZodDefault<z.ZodNumber>;
71
+ chainWithinProject: z.ZodDefault<z.ZodBoolean>;
72
+ fanUpstream: z.ZodDefault<z.ZodBoolean>;
73
+ }, z.core.$strip>>;
74
+ tools: z.ZodDefault<z.ZodArray<z.ZodEnum<{
75
+ omni: "omni";
76
+ turbo: "turbo";
77
+ nx: "nx";
78
+ moon: "moon";
79
+ }>>>;
80
+ versions: z.ZodPrefault<z.ZodObject<{
81
+ turbo: z.ZodDefault<z.ZodString>;
82
+ nx: z.ZodDefault<z.ZodString>;
83
+ moon: z.ZodDefault<z.ZodString>;
84
+ bun: z.ZodDefault<z.ZodString>;
85
+ }, z.core.$strip>>;
86
+ }, z.core.$strip>;
87
+ export type HarnessConfig = z.infer<typeof HarnessConfigSchema>;
88
+ export type HarnessConfigInput = z.input<typeof HarnessConfigSchema>;
89
+ /** Parse and fill defaults for a (possibly partial) harness config. */
90
+ export declare function resolveConfig(input?: HarnessConfigInput): HarnessConfig;
91
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,gEAMxB,CAAC;AAEX,eAAO,MAAM,wBAAwB;;;;;;EAAgC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E,gFAAgF;AAChF,eAAO,MAAM,KAAK,0CAA2C,CAAC;AAC9D,eAAO,MAAM,UAAU;;;;;EAAgB,CAAC;AACxC,MAAM,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC;AAE9C,eAAO,MAAM,sBAAsB;;;;;;;;;;;kBA0BlB,CAAC;AAElB,eAAO,MAAM,gBAAgB;;;;;;kBAmCZ,CAAC;AAElB,eAAO,MAAM,oBAAoB;;;;;kBAOhB,CAAC;AAElB,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgC9B,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAErE,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,kBAAkB,GAAG,aAAa,CAEvE"}
@@ -0,0 +1,19 @@
1
+ import { HarnessConfig, HarnessConfigInput } from '../config';
2
+ import { ProjectNode } from '../graph';
3
+ export interface GenerateResult {
4
+ rootDir: string;
5
+ config: HarnessConfig;
6
+ projects: ProjectNode[];
7
+ /** Every file written, workspace-relative. */
8
+ files: string[];
9
+ }
10
+ /**
11
+ * Generate a complete benchmark workspace at `rootDir`. Existing contents at
12
+ * `rootDir` are removed first so the workspace is reproducible.
13
+ *
14
+ * The generator only produces the neutral, tool-agnostic workspace (projects,
15
+ * sources, task runners). Each enabled tool then writes its own configuration
16
+ * via its adapter, keeping the tools fully decoupled.
17
+ */
18
+ export declare function generateWorkspace(rootDir: string, input?: HarnessConfigInput): Promise<GenerateResult>;
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generate/index.ts"],"names":[],"mappings":"AAGA,OAAO,EACH,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAE1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAc,KAAK,WAAW,EAAa,MAAM,UAAU,CAAC;AAInE,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,8CAA8C;IAC9C,KAAK,EAAE,MAAM,EAAE,CAAC;CACnB;AAsHD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACnC,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,cAAc,CAAC,CAmEzB"}
@@ -0,0 +1,20 @@
1
+ import { HarnessConfig } from '../config';
2
+ import { ProjectNode } from '../graph';
3
+ /**
4
+ * The minimal source file each project ships. It exists so that every runner
5
+ * has a real input file to hash for cache keys.
6
+ */
7
+ export declare function sourceFile(project: ProjectNode): string;
8
+ /**
9
+ * The task runner every project uses. It is intentionally tiny but does real
10
+ * work that exercises each runner's caching + log-capture machinery:
11
+ * - reads its own source (a cache input),
12
+ * - performs a deterministic CPU loop,
13
+ * - prints a configurable number of log lines to stdout,
14
+ * - writes deterministic output file(s) into dist/ (cache outputs).
15
+ *
16
+ * Determinism matters: identical inputs => identical outputs => cache hits on
17
+ * warm runs, which is exactly what we want to measure.
18
+ */
19
+ export declare function taskRunner(config: HarnessConfig, project: ProjectNode): string;
20
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/generate/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CASvD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CACtB,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,WAAW,GACrB,MAAM,CAsDR"}
@@ -0,0 +1,21 @@
1
+ import { HarnessConfig } from './config';
2
+ /** A single generated project and the upstream projects it depends on. */
3
+ export interface ProjectNode {
4
+ /** Zero-based index in the generated set. */
5
+ index: number;
6
+ /** Package name, e.g. `bench-p0007`. */
7
+ name: string;
8
+ /** Workspace-relative POSIX directory, e.g. `packages/bench-p0007`. */
9
+ dir: string;
10
+ /** Indices of upstream projects this project depends on. */
11
+ dependencies: number[];
12
+ }
13
+ export declare function projectName(config: HarnessConfig, index: number): string;
14
+ /**
15
+ * Build the full project graph for a config. Dependencies always point to
16
+ * lower indices, guaranteeing an acyclic graph.
17
+ */
18
+ export declare function buildGraph(config: HarnessConfig): ProjectNode[];
19
+ /** The task names generated for every project: `t0`, `t1`, ... */
20
+ export declare function taskNames(config: HarnessConfig): string[];
21
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IACxB,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,YAAY,EAAE,MAAM,EAAE,CAAC;CAC1B;AAmBD,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGxE;AA+DD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,EAAE,CAa/D;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,EAAE,CAEzD"}
@@ -0,0 +1,16 @@
1
+ export * from './bench';
2
+ export { installWorkspace } from './bench/install';
3
+ export { formatReport } from './bench/report';
4
+ export type { Stats } from './bench/stats';
5
+ export { computeStats, formatMs } from './bench/stats';
6
+ export * from './config';
7
+ export type { GenerateResult } from './generate';
8
+ export { generateWorkspace } from './generate';
9
+ export * from './graph';
10
+ export type { RunSuiteOptions, SuiteEvent, SuiteResult, SuiteScenarioResult, } from './suite';
11
+ export { runSuite } from './suite';
12
+ export * from './suite/preset';
13
+ export { formatSuiteMarkdown } from './suite/report';
14
+ export type { GenerationContext, RunInvocation, ToolAdapter, ToolContext, WorkspaceWriter, } from './tools';
15
+ export { assertSupportedVersion, getAdapter, getAdapters, resolveToolVersions, } from './tools';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACvD,cAAc,UAAU,CAAC;AACzB,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,cAAc,SAAS,CAAC;AACxB,YAAY,EACR,eAAe,EACf,UAAU,EACV,WAAW,EACX,mBAAmB,GACtB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EACR,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,WAAW,EACX,eAAe,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EACH,sBAAsB,EACtB,UAAU,EACV,WAAW,EACX,mBAAmB,GACtB,MAAM,SAAS,CAAC"}