@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
@@ -0,0 +1,76 @@
1
+ import { satisfies } from "semver";
2
+ import type { HarnessConfig, Tool } from "../config";
3
+ import { moonAdapter } from "./moon";
4
+ import { nxAdapter } from "./nx";
5
+ import { omniAdapter } from "./omni";
6
+ import { turboAdapter } from "./turbo";
7
+ import type { ToolAdapter } from "./types";
8
+
9
+ const ADAPTERS: Record<Tool, ToolAdapter> = {
10
+ omni: omniAdapter,
11
+ turbo: turboAdapter,
12
+ nx: nxAdapter,
13
+ moon: moonAdapter,
14
+ };
15
+
16
+ export function getAdapter(tool: Tool): ToolAdapter {
17
+ return ADAPTERS[tool];
18
+ }
19
+
20
+ export function getAdapters(tools: Tool[]): ToolAdapter[] {
21
+ return tools.map(getAdapter);
22
+ }
23
+
24
+ /** Throw if `version` does not satisfy any of the adapter's supported ranges. */
25
+ export function assertSupportedVersion(
26
+ adapter: ToolAdapter,
27
+ version: string,
28
+ ): void {
29
+ const ok = adapter.supportedVersions.some((range) =>
30
+ satisfies(version, range, { includePrerelease: true, loose: true }),
31
+ );
32
+ if (!ok) {
33
+ throw new Error(
34
+ `${adapter.tool} version "${version}" is not supported by task-bench ` +
35
+ `(supported: ${adapter.supportedVersions.join(" || ")}). ` +
36
+ `Adjust versions.${adapter.tool} in your config or upgrade/downgrade the tool.`,
37
+ );
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Resolve each enabled tool's version (pinned via config, or detected for
43
+ * external tools) and validate it against the adapter's supported ranges.
44
+ * Throws on the first unsupported version. Returns a map of tool -> version.
45
+ */
46
+ export async function resolveToolVersions(
47
+ config: HarnessConfig,
48
+ rootDir: string,
49
+ tools: Tool[] = config.tools,
50
+ ): Promise<Map<Tool, string | null>> {
51
+ const versions = new Map<Tool, string | null>();
52
+ for (const tool of tools) {
53
+ const adapter = getAdapter(tool);
54
+ let version = adapter.pinnedVersion(config);
55
+ if (version === null && adapter.detectVersion) {
56
+ version = await adapter.detectVersion(rootDir);
57
+ }
58
+ if (version !== null) {
59
+ assertSupportedVersion(adapter, version);
60
+ }
61
+ versions.set(tool, version);
62
+ }
63
+ return versions;
64
+ }
65
+
66
+ export {
67
+ moonAdapter,
68
+ moonProjectConfig,
69
+ moonTaskDependencies,
70
+ moonToolchainConfig,
71
+ moonWorkspaceConfig,
72
+ } from "./moon";
73
+ export { nxAdapter, nxProjectConfig, nxRootConfig } from "./nx";
74
+ export { omniAdapter, omniProjectConfig, omniWorkspaceConfig } from "./omni";
75
+ export { turboAdapter, turboRootConfig } from "./turbo";
76
+ export * from "./types";
@@ -0,0 +1,106 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { stringify as stringifyYaml } from "yaml";
4
+ import type { HarnessConfig } from "../config";
5
+ import { type ProjectNode, taskNames } from "../graph";
6
+ import {
7
+ dependencyNames,
8
+ type GenerationContext,
9
+ removeDist,
10
+ resolveBin,
11
+ type ToolAdapter,
12
+ type ToolContext,
13
+ } from "./types";
14
+
15
+ const WORKSPACE_SCHEMA = "https://moonrepo.dev/schemas/workspace.json";
16
+ const TOOLCHAIN_SCHEMA = "https://moonrepo.dev/schemas/toolchain.json";
17
+ const PROJECT_SCHEMA = "https://moonrepo.dev/schemas/project.json";
18
+
19
+ /** moon dependency edges for task index `k`, using moon's target syntax. */
20
+ export function moonTaskDependencies(
21
+ config: HarnessConfig,
22
+ k: number,
23
+ ): string[] {
24
+ const deps: string[] = [];
25
+ if (config.task.chainWithinProject && k > 0) deps.push(`~:t${k - 1}`);
26
+ if (config.task.fanUpstream) deps.push(`^:t${k}`);
27
+ return deps;
28
+ }
29
+
30
+ export function moonWorkspaceConfig(): string {
31
+ return stringifyYaml({
32
+ $schema: WORKSPACE_SCHEMA,
33
+ projects: ["packages/*"],
34
+ });
35
+ }
36
+
37
+ export function moonToolchainConfig(): string {
38
+ // Intentionally empty: no managed toolchain, so moon runs `node` from the
39
+ // system PATH (kept equivalent to the other runners).
40
+ return stringifyYaml({ $schema: TOOLCHAIN_SCHEMA });
41
+ }
42
+
43
+ export function moonProjectConfig(
44
+ config: HarnessConfig,
45
+ project: ProjectNode,
46
+ projects: ProjectNode[],
47
+ ): string {
48
+ const tasks: Record<string, unknown> = {};
49
+ taskNames(config).forEach((task, k) => {
50
+ const deps = moonTaskDependencies(config, k);
51
+ tasks[task] = {
52
+ command: `node ./task.mjs ${task}`,
53
+ ...(deps.length ? { deps } : {}),
54
+ inputs: ["package.json", "task.mjs", "src/**/*"],
55
+ outputs: [`dist/${task}.*`],
56
+ };
57
+ });
58
+
59
+ const doc: Record<string, unknown> = {
60
+ $schema: PROJECT_SCHEMA,
61
+ id: project.name,
62
+ layer: "library",
63
+ language: "javascript",
64
+ ...(project.dependencies.length
65
+ ? { dependsOn: dependencyNames(project, projects) }
66
+ : {}),
67
+ tasks,
68
+ };
69
+
70
+ return stringifyYaml(doc);
71
+ }
72
+
73
+ export const moonAdapter: ToolAdapter = {
74
+ tool: "moon",
75
+ hasDaemon: false,
76
+ supportedVersions: ["^2.0.0"],
77
+
78
+ pinnedVersion: (config) => config.versions.moon,
79
+ devDependencies: (config) => ({ "@moonrepo/cli": config.versions.moon }),
80
+ setup: async (ctx: GenerationContext) => {
81
+ await ctx.write(".moon/workspace.yml", moonWorkspaceConfig());
82
+ await ctx.write(".moon/toolchain.yml", moonToolchainConfig());
83
+ for (const project of ctx.projects) {
84
+ await ctx.write(
85
+ `${project.dir}/moon.yml`,
86
+ moonProjectConfig(ctx.config, project, ctx.projects),
87
+ );
88
+ }
89
+ },
90
+
91
+ run: (task, ctx) => ({
92
+ file: resolveBin(ctx.rootDir, "moon"),
93
+ args: ["run", `:${task}`, "--concurrency", String(ctx.concurrency)],
94
+ }),
95
+ env: () => ({}),
96
+ clearCaches: async (ctx: ToolContext) => {
97
+ await removeDist(ctx);
98
+ await rm(join(ctx.rootDir, ".moon", "cache"), {
99
+ recursive: true,
100
+ force: true,
101
+ });
102
+ },
103
+ stopDaemon: async () => {
104
+ // moon has no persistent daemon.
105
+ },
106
+ };
@@ -0,0 +1,106 @@
1
+ import { execa } from "execa";
2
+ import type { HarnessConfig } from "../config";
3
+ import { type ProjectNode, taskNames } from "../graph";
4
+ import {
5
+ type GenerationContext,
6
+ removeDist,
7
+ resolveBin,
8
+ type ToolAdapter,
9
+ type ToolContext,
10
+ taskDependencies,
11
+ } from "./types";
12
+
13
+ export function nxRootConfig(config: HarnessConfig): string {
14
+ const targetDefaults: Record<string, unknown> = {};
15
+ taskNames(config).forEach((task, k) => {
16
+ targetDefaults[task] = {
17
+ dependsOn: taskDependencies(config, k),
18
+ cache: true,
19
+ outputs: [`{projectRoot}/dist/${task}.*`],
20
+ inputs: [
21
+ "{projectRoot}/package.json",
22
+ "{projectRoot}/task.mjs",
23
+ "{projectRoot}/src/**/*",
24
+ ],
25
+ };
26
+ });
27
+ return `${JSON.stringify(
28
+ {
29
+ $schema: "./node_modules/nx/schemas/nx-schema.json",
30
+ targetDefaults,
31
+ },
32
+ null,
33
+ 2,
34
+ )}\n`;
35
+ }
36
+
37
+ export function nxProjectConfig(
38
+ config: HarnessConfig,
39
+ project: ProjectNode,
40
+ ): string {
41
+ const targets: Record<string, unknown> = {};
42
+ for (const task of taskNames(config)) {
43
+ targets[task] = {
44
+ executor: "nx:run-commands",
45
+ options: { command: `node ./task.mjs ${task}`, cwd: project.dir },
46
+ };
47
+ }
48
+ return `${JSON.stringify(
49
+ {
50
+ name: project.name,
51
+ $schema: "../../node_modules/nx/schemas/project-schema.json",
52
+ targets,
53
+ },
54
+ null,
55
+ 2,
56
+ )}\n`;
57
+ }
58
+
59
+ export const nxAdapter: ToolAdapter = {
60
+ tool: "nx",
61
+ hasDaemon: true,
62
+ supportedVersions: [">=21.0.0 <24.0.0"],
63
+
64
+ pinnedVersion: (config) => config.versions.nx,
65
+ devDependencies: (config) => ({ nx: config.versions.nx }),
66
+ setup: async (ctx: GenerationContext) => {
67
+ await ctx.write("nx.json", nxRootConfig(ctx.config));
68
+ for (const project of ctx.projects) {
69
+ await ctx.write(
70
+ `${project.dir}/project.json`,
71
+ nxProjectConfig(ctx.config, project),
72
+ );
73
+ }
74
+ },
75
+
76
+ run: (task, ctx) => ({
77
+ file: resolveBin(ctx.rootDir, "nx"),
78
+ args: ["run-many", "-t", task, `--parallel=${ctx.concurrency}`],
79
+ }),
80
+ env: (ctx) => ({
81
+ NX_DAEMON: ctx.daemon ? "true" : "false",
82
+ NX_TUI: "false",
83
+ }),
84
+ clearCaches: async (ctx: ToolContext) => {
85
+ await removeDist(ctx);
86
+ // nx 23 stores task results in a DB under `.nx/workspace-data` (used
87
+ // even in no-daemon mode), which `--onlyCache` does NOT clear. A full
88
+ // `nx reset` is the only reliable way to force a genuinely cold run.
89
+ // clearCaches is only invoked for cold runs, so tearing down the daemon
90
+ // too is acceptable (warm runs never call this).
91
+ await execa(resolveBin(ctx.rootDir, "nx"), ["reset"], {
92
+ cwd: ctx.rootDir,
93
+ reject: false,
94
+ stdio: "ignore",
95
+ env: { NX_DAEMON: ctx.daemon ? "true" : "false", NX_TUI: "false" },
96
+ });
97
+ },
98
+ stopDaemon: async (ctx: ToolContext) => {
99
+ await execa(resolveBin(ctx.rootDir, "nx"), ["reset"], {
100
+ cwd: ctx.rootDir,
101
+ reject: false,
102
+ stdio: "ignore",
103
+ env: { NX_DAEMON: "false" },
104
+ });
105
+ },
106
+ };
@@ -0,0 +1,96 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { execa } from "execa";
4
+ import { stringify as stringifyYaml } from "yaml";
5
+ import type { HarnessConfig } from "../config";
6
+ import { type ProjectNode, taskNames } from "../graph";
7
+ import {
8
+ dependencyNames,
9
+ type GenerationContext,
10
+ removeDist,
11
+ type ToolAdapter,
12
+ type ToolContext,
13
+ taskDependencies,
14
+ } from "./types";
15
+
16
+ const SCHEMA =
17
+ "# yaml-language-server: $schema=https://raw.githubusercontent.com/omni-oss/json-schemas/refs/heads/main/project.json";
18
+ const WORKSPACE_SCHEMA =
19
+ "# yaml-language-server: $schema=https://raw.githubusercontent.com/omni-oss/json-schemas/refs/heads/main/workspace.json";
20
+
21
+ export function omniWorkspaceConfig(): string {
22
+ const body = stringifyYaml({ ui: "stream", projects: ["packages/*"] });
23
+ return `${WORKSPACE_SCHEMA}\n${body}`;
24
+ }
25
+
26
+ export function omniProjectConfig(
27
+ config: HarnessConfig,
28
+ project: ProjectNode,
29
+ projects: ProjectNode[],
30
+ ): string {
31
+ const tasks: Record<string, unknown> = {};
32
+ taskNames(config).forEach((task, k) => {
33
+ const deps = taskDependencies(config, k);
34
+ tasks[task] = {
35
+ exec: `node ./task.mjs ${task}`,
36
+ ...(deps.length ? { dependencies: deps } : {}),
37
+ cache: { output: { files: [`dist/${task}.*`] } },
38
+ };
39
+ });
40
+
41
+ const doc: Record<string, unknown> = {
42
+ name: project.name,
43
+ ...(project.dependencies.length
44
+ ? { dependencies: dependencyNames(project, projects) }
45
+ : {}),
46
+ cache: { key: { files: ["package.json", "task.mjs", "src/**/*.js"] } },
47
+ tasks,
48
+ };
49
+
50
+ return `${SCHEMA}\n${stringifyYaml(doc)}`;
51
+ }
52
+
53
+ export const omniAdapter: ToolAdapter = {
54
+ tool: "omni",
55
+ hasDaemon: false,
56
+ // omni is a host-provided binary, not installed per-workspace.
57
+ supportedVersions: [">=0.16.0"],
58
+
59
+ pinnedVersion: () => null,
60
+ detectVersion: async (rootDir) => {
61
+ const result = await execa("omni", ["--version"], {
62
+ cwd: rootDir,
63
+ reject: false,
64
+ });
65
+ const match = `${result.stdout} ${result.stderr}`.match(
66
+ /(\d+\.\d+\.\d+[\w.-]*)/,
67
+ );
68
+ return match?.[1] ?? null;
69
+ },
70
+ devDependencies: () => ({}),
71
+ setup: async (ctx: GenerationContext) => {
72
+ await ctx.write("workspace.omni.yaml", omniWorkspaceConfig());
73
+ for (const project of ctx.projects) {
74
+ await ctx.write(
75
+ `${project.dir}/project.omni.yaml`,
76
+ omniProjectConfig(ctx.config, project, ctx.projects),
77
+ );
78
+ }
79
+ },
80
+
81
+ run: (task, ctx) => ({
82
+ file: "omni",
83
+ args: ["run", task, "-u", "stream", "-c", String(ctx.concurrency)],
84
+ }),
85
+ env: () => ({}),
86
+ clearCaches: async (ctx: ToolContext) => {
87
+ await removeDist(ctx);
88
+ await rm(join(ctx.rootDir, ".omni", "cache"), {
89
+ recursive: true,
90
+ force: true,
91
+ });
92
+ },
93
+ stopDaemon: async () => {
94
+ // omni has no persistent daemon.
95
+ },
96
+ };
@@ -0,0 +1,78 @@
1
+ import { rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { execa } from "execa";
4
+ import type { HarnessConfig } from "../config";
5
+ import { taskNames } from "../graph";
6
+ import {
7
+ type GenerationContext,
8
+ removeDist,
9
+ resolveBin,
10
+ type ToolAdapter,
11
+ type ToolContext,
12
+ taskDependencies,
13
+ } from "./types";
14
+
15
+ export function turboRootConfig(config: HarnessConfig): string {
16
+ const tasks: Record<string, unknown> = {};
17
+ taskNames(config).forEach((task, k) => {
18
+ tasks[task] = {
19
+ dependsOn: taskDependencies(config, k),
20
+ outputs: [`dist/${task}.*`],
21
+ inputs: ["package.json", "task.mjs", "src/**"],
22
+ };
23
+ });
24
+ return `${JSON.stringify(
25
+ {
26
+ $schema: "https://turbo.build/schema.json",
27
+ // Turbo 2.x defaults to strict env mode, which would strip the
28
+ // benchmark's execution-marker var. Pass it through (not hashed)
29
+ // so cache-hit verification works without perturbing cache keys.
30
+ globalPassThroughEnv: ["TASK_BENCH_EXEC_LOG"],
31
+ tasks,
32
+ },
33
+ null,
34
+ 2,
35
+ )}\n`;
36
+ }
37
+
38
+ export const turboAdapter: ToolAdapter = {
39
+ tool: "turbo",
40
+ hasDaemon: true,
41
+ supportedVersions: ["^2.0.0"],
42
+
43
+ pinnedVersion: (config) => config.versions.turbo,
44
+ devDependencies: (config) => ({ turbo: config.versions.turbo }),
45
+ setup: async (ctx: GenerationContext) => {
46
+ await ctx.write("turbo.json", turboRootConfig(ctx.config));
47
+ },
48
+
49
+ run: (task, ctx) => ({
50
+ file: resolveBin(ctx.rootDir, "turbo"),
51
+ args: [
52
+ "run",
53
+ task,
54
+ "--log-order=stream",
55
+ `--concurrency=${ctx.concurrency}`,
56
+ ctx.daemon ? "--daemon" : "--no-daemon",
57
+ ],
58
+ }),
59
+ env: () => ({}),
60
+ clearCaches: async (ctx: ToolContext) => {
61
+ await removeDist(ctx);
62
+ await rm(join(ctx.rootDir, ".turbo"), {
63
+ recursive: true,
64
+ force: true,
65
+ });
66
+ await rm(join(ctx.rootDir, "node_modules", ".cache", "turbo"), {
67
+ recursive: true,
68
+ force: true,
69
+ });
70
+ },
71
+ stopDaemon: async (ctx: ToolContext) => {
72
+ await execa(resolveBin(ctx.rootDir, "turbo"), ["daemon", "stop"], {
73
+ cwd: ctx.rootDir,
74
+ reject: false,
75
+ stdio: "ignore",
76
+ });
77
+ },
78
+ };
@@ -0,0 +1,116 @@
1
+ import { existsSync } from "node:fs";
2
+ import { rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import type { HarnessConfig, Tool } from "../config";
5
+ import type { ProjectNode } from "../graph";
6
+
7
+ /** Writes a workspace-relative file (creating parent dirs) and records it. */
8
+ export type WorkspaceWriter = (
9
+ relPath: string,
10
+ contents: string,
11
+ ) => Promise<void>;
12
+
13
+ /** Everything a tool needs to write its configuration after generation. */
14
+ export interface GenerationContext {
15
+ /** Absolute path to the generated workspace root. */
16
+ rootDir: string;
17
+ config: HarnessConfig;
18
+ /** All generated projects, in index order. */
19
+ projects: ProjectNode[];
20
+ /** The resolved version of this tool (pinned or detected), if known. */
21
+ version: string | null;
22
+ /** Write a file into the workspace. */
23
+ write: WorkspaceWriter;
24
+ }
25
+
26
+ /** Runtime context for executing/benchmarking a tool. */
27
+ export interface ToolContext {
28
+ /** Absolute path to the generated workspace root. */
29
+ rootDir: string;
30
+ /** Workspace-relative project directories, e.g. `packages/bench-p0001`. */
31
+ projectDirs: string[];
32
+ /** Concurrency applied identically to every runner. */
33
+ concurrency: number;
34
+ /** Whether the tool's persistent daemon (if any) is allowed. */
35
+ daemon: boolean;
36
+ }
37
+
38
+ export interface RunInvocation {
39
+ file: string;
40
+ args: string[];
41
+ }
42
+
43
+ /**
44
+ * A self-contained integration for one task runner. Each adapter owns:
45
+ * - which tool versions it supports (`supportedVersions`),
46
+ * - the npm dependencies it needs (`devDependencies`),
47
+ * - how to write its own configuration (`setup`),
48
+ * - and how to run / reset / clean it up at benchmark time.
49
+ * This keeps every tool decoupled from the generator and from each other.
50
+ */
51
+ export interface ToolAdapter {
52
+ tool: Tool;
53
+ /** Whether this runner has a persistent daemon that can boost warm perf. */
54
+ hasDaemon: boolean;
55
+ /** Semver ranges of the tool version this adapter supports. */
56
+ supportedVersions: readonly string[];
57
+
58
+ /** Version pinned via config for installable tools; null for external ones. */
59
+ pinnedVersion(config: HarnessConfig): string | null;
60
+ /** Detect the installed version of an external/global tool (e.g. omni). */
61
+ detectVersion?(rootDir: string): Promise<string | null>;
62
+ /** npm devDependencies to add to the root package.json. */
63
+ devDependencies(config: HarnessConfig): Record<string, string>;
64
+ /** Derive and write this tool's config files from the generated projects. */
65
+ setup(ctx: GenerationContext): Promise<void>;
66
+
67
+ /** Command that runs `task` across every project in the workspace. */
68
+ run(task: string, ctx: ToolContext): RunInvocation;
69
+ /** Extra environment variables for each invocation (e.g. daemon toggles). */
70
+ env(ctx: ToolContext): Record<string, string>;
71
+ /** Remove caches and all task outputs. */
72
+ clearCaches(ctx: ToolContext): Promise<void>;
73
+ /** Stop the persistent daemon, if any. Used for cold runs and cleanup. */
74
+ stopDaemon(ctx: ToolContext): Promise<void>;
75
+ }
76
+
77
+ /** Resolve a locally-installed binary, falling back to the global name. */
78
+ export function resolveBin(rootDir: string, name: string): string {
79
+ const local = join(rootDir, "node_modules", ".bin", name);
80
+ return existsSync(local) ? local : name;
81
+ }
82
+
83
+ /** Remove the `dist/` output directory of every project. */
84
+ export async function removeDist(ctx: ToolContext): Promise<void> {
85
+ await Promise.all(
86
+ ctx.projectDirs.map((dir) =>
87
+ rm(join(ctx.rootDir, dir, "dist"), {
88
+ recursive: true,
89
+ force: true,
90
+ }),
91
+ ),
92
+ );
93
+ }
94
+
95
+ /**
96
+ * Dependency edges for task index `k`, shared by omni/turbo/nx so their graphs
97
+ * stay equivalent:
98
+ * - within-project: `t{k-1}` (if enabled and k > 0)
99
+ * - upstream: `^t{k}` (if enabled)
100
+ */
101
+ export function taskDependencies(config: HarnessConfig, k: number): string[] {
102
+ const deps: string[] = [];
103
+ if (config.task.chainWithinProject && k > 0) deps.push(`t${k - 1}`);
104
+ if (config.task.fanUpstream) deps.push(`^t${k}`);
105
+ return deps;
106
+ }
107
+
108
+ /** Map upstream project indices to their names for a project's dependencies. */
109
+ export function dependencyNames(
110
+ project: ProjectNode,
111
+ projects: ProjectNode[],
112
+ ): string[] {
113
+ return project.dependencies.map((i) => projects[i]?.name ?? "");
114
+ }
115
+
116
+ export type { Tool };
package/tsconfig.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": ["@omni-oss/tsconfig/with-paths.json", "./tsconfig.project.json"],
3
+ "compilerOptions": {}
4
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist",
4
+ "types": ["node"]
5
+ }
6
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": ["@omni-oss/tsconfig/types.json", "./tsconfig.project.json"],
3
+ "compilerOptions": {}
4
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,29 @@
1
+
2
+ import { createConfig } from "@omni-oss/vite-config/script";
3
+
4
+
5
+ import packageJson from "./package.json";
6
+
7
+ export default createConfig({
8
+ package: packageJson,
9
+
10
+ generateTypes: true,
11
+ externalizeDeps: true,
12
+
13
+ overrides: {
14
+ build: {
15
+ lib: {
16
+ entry: {
17
+
18
+ "task-bench-cli": "src/cli/index.ts",
19
+
20
+ "index": "src/index.ts",
21
+ },
22
+ formats: ["es"],
23
+ fileName: (format, entryName) =>
24
+ `${entryName || "task-bench"}.${format === "cjs" ? "cjs" : "mjs"}`,
25
+ name: "TaskBench",
26
+ },
27
+ },
28
+ },
29
+ });
@@ -0,0 +1,13 @@
1
+ import { mergeConfig, type UserWorkspaceConfig } from "vitest/config";
2
+ import baseConfig from "./vite.config";
3
+ import unitTestConfig from "@omni-oss/vitest-config/unit";
4
+
5
+ export default mergeConfig(mergeConfig(baseConfig, unitTestConfig), {
6
+ test: {
7
+ testTimeout: 1000,
8
+ include: ["./src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
9
+ exclude: [
10
+ "./src/**/__tests__/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
11
+ ],
12
+ },
13
+ } satisfies UserWorkspaceConfig);