@visulima/task-runner 1.0.0-alpha.3 → 1.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +193 -51
  3. package/dist/affected.d.ts +37 -3
  4. package/dist/command-parser/expand-arguments.d.ts +11 -0
  5. package/dist/command-parser/expand-shortcut.d.ts +15 -0
  6. package/dist/command-parser/expand-wildcard.d.ts +13 -0
  7. package/dist/command-parser/index.d.ts +18 -0
  8. package/dist/command-parser/strip-quotes.d.ts +6 -0
  9. package/dist/concurrent-fallback.d.ts +16 -0
  10. package/dist/concurrent.d.ts +23 -0
  11. package/dist/detect-shell.d.ts +19 -0
  12. package/dist/flow-controllers/index.d.ts +7 -0
  13. package/dist/flow-controllers/input-handler.d.ts +44 -0
  14. package/dist/flow-controllers/log-timings.d.ts +18 -0
  15. package/dist/flow-controllers/restart-process.d.ts +21 -0
  16. package/dist/flow-controllers/teardown.d.ts +22 -0
  17. package/dist/index.d.ts +13 -3
  18. package/dist/index.js +26 -12
  19. package/dist/native-binding.d.ts +44 -2
  20. package/dist/packem_shared/{Cache-IYpTYVUC.js → Cache-C23LywYn.js} +2 -3
  21. package/dist/packem_shared/{FingerprintManager-D6Y0erg-.js → FingerprintManager-Cu-ta9ee.js} +0 -1
  22. package/dist/packem_shared/{IncrementalFileHasher-Ds3J6dgb.js → IncrementalFileHasher-Cm_kJY5V.js} +1 -1
  23. package/dist/packem_shared/{TaskOrchestrator-BvYs3ONw.js → TaskOrchestrator-lLn-PH1m.js} +2 -5
  24. package/dist/packem_shared/TerminalBuffer-D6zP2zLh.js +250 -0
  25. package/dist/packem_shared/{filterAffectedTasks-I-18zPg6.js → buildForwardDependencyMap-Cu08NWB1.js} +58 -20
  26. package/dist/packem_shared/{computeTaskHash-BoCnnvIJ.js → computeTaskHash-B2SVZqgp.js} +1 -2
  27. package/dist/packem_shared/createInputHandler-DTfePcTG.js +37 -0
  28. package/dist/packem_shared/{defaultTaskRunner-CrW4v5Ye.js → defaultTaskRunner-X1MIynHu.js} +6 -7
  29. package/dist/packem_shared/detectScriptShell-CR-xXKA4.js +53 -0
  30. package/dist/packem_shared/enforceProjectConstraints-_Ej0zHch.js +90 -0
  31. package/dist/packem_shared/expandArguments-0AwD2BIA.js +26 -0
  32. package/dist/packem_shared/expandShortcut-BVG05ee4.js +23 -0
  33. package/dist/packem_shared/expandWildcard-B0xN_knq.js +107 -0
  34. package/dist/packem_shared/{findCycle-DF4_BRdO.js → findCycle-DefgNYhg.js} +1 -1
  35. package/dist/packem_shared/formatTimingTable-3qtCM552.js +46 -0
  36. package/dist/packem_shared/isNativeAvailable-BpD28A6Z.js +44 -0
  37. package/dist/packem_shared/parseCommands-D-IgF8Zh.js +26 -0
  38. package/dist/packem_shared/{TaskScheduler-CJilHDta.js → parsePartition-C4-P5RjK.js} +44 -1
  39. package/dist/packem_shared/{projectGraphToDot-VdTjHcVp.js → projectGraphToDot-C8uYeaPo.js} +20 -3
  40. package/dist/packem_shared/runConcurrentFallback-3q46z4AS.js +357 -0
  41. package/dist/packem_shared/runConcurrently-ATDwJNR6.js +67 -0
  42. package/dist/packem_shared/runTeardown-BAezH79J.js +49 -0
  43. package/dist/packem_shared/stripQuotes-Cey-zwFf.js +9 -0
  44. package/dist/packem_shared/withRestart-BREjRJa4.js +49 -0
  45. package/dist/project-constraints.d.ts +16 -0
  46. package/dist/task-scheduler.d.ts +23 -0
  47. package/dist/terminal-buffer.d.ts +29 -0
  48. package/dist/types.d.ts +220 -1
  49. package/index.js +599 -0
  50. package/package.json +14 -13
  51. package/binding.js +0 -204
  52. package/dist/packem_shared/isNativeAvailable-BWhnZ4ES.js +0 -19
  53. package/dist/packem_shared/{RemoteCache-BDqrnDEi.js → RemoteCache-BFceSe4a.js} +1 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,44 @@
1
+ ## @visulima/task-runner [1.0.0-alpha.4](https://github.com/visulima/visulima/compare/@visulima/task-runner@1.0.0-alpha.3...@visulima/task-runner@1.0.0-alpha.4) (2026-04-08)
2
+
3
+ ### Features
4
+
5
+ * Add native Rust bindings for package manager operations ([#596](https://github.com/visulima/visulima/issues/596)) ([2ec22d0](https://github.com/visulima/visulima/commit/2ec22d023eade3fed67fb811696fbd8f7b52569d))
6
+ * **task-runner, vis:** project constraints, CI partitioning, affected scopes ([29295e9](https://github.com/visulima/visulima/commit/29295e989ecdfe2019469d1917a6c90a92e17bcf))
7
+ * **task-runner:** add concurrent process runner with Rust NAPI bindings ([c4f5d93](https://github.com/visulima/visulima/commit/c4f5d930e81a5eb641ceb3ab925c3b10a885bb6a))
8
+ * **vis:** expand devcontainer command with templates, validation, and config properties ([807e730](https://github.com/visulima/visulima/commit/807e730a43f0ea644d016b4f5506706972d2ff41))
9
+ * **vis:** group CLI commands into logical categories for help output ([0a4cac8](https://github.com/visulima/visulima/commit/0a4cac859c8edf7aacdacca7b9a03219967d525a))
10
+ * **vis:** replace inline TUI with full-screen Nx-style interactive task runner ([1409aad](https://github.com/visulima/visulima/commit/1409aad879c713051bba12298a3feb1d5ba852f2))
11
+
12
+ ### Bug Fixes
13
+
14
+ * **ci:** make native-binding tests work with and without compiled binary ([9a40fb4](https://github.com/visulima/visulima/commit/9a40fb40d5cba9fcd2e0176eea8b7bf8d9792c7d))
15
+ * **task-runner,tui:** guard null native events and increase CI test timeout ([e76a791](https://github.com/visulima/visulima/commit/e76a791d90043537e08be0545f706e35acaa555d))
16
+ * **task-runner:** deno.json support, Windows Job Objects, docs, and review fixes ([4fb27f0](https://github.com/visulima/visulima/commit/4fb27f081b4b50b41dbb86b5b1a962b63f7a6df3))
17
+ * **task-runner:** fix Windows cross-compilation by upgrading windows-sys to 0.61 ([b56b95e](https://github.com/visulima/visulima/commit/b56b95e2a39ca972398859e6eb87e528f4463d97))
18
+ * **task-runner:** resolve eslint errors ([f0a21a6](https://github.com/visulima/visulima/commit/f0a21a689bc9e1d8b091a513e21cb11b77103ba4))
19
+ * **task-runner:** use JS fallback for onEvent streaming, fix StaticRender ref ([1a7165c](https://github.com/visulima/visulima/commit/1a7165cd9eb71472895cd08682983fa25703dc93))
20
+ * **tsconfig:** add node types and fix implicit any parameter ([1744d82](https://github.com/visulima/visulima/commit/1744d82a07fca03f2e6ff660b918e9b2623acf69))
21
+
22
+ ### Miscellaneous Chores
23
+
24
+ * added og images ([02d9d1e](https://github.com/visulima/visulima/commit/02d9d1e47be3ce75679ea89e857dc4e4bfe4946b))
25
+ * **task-runner:** add tsconfig.eslint.json for type-aware linting ([83e0bf2](https://github.com/visulima/visulima/commit/83e0bf23511a169b801f6edf652a8be7ee968c24))
26
+ * **task-runner:** apply prettier formatting ([521afc2](https://github.com/visulima/visulima/commit/521afc22d94a2626c7246062cecfc0627f929ee4))
27
+ * **task-runner:** expand inline if-return to block syntax ([0f48a96](https://github.com/visulima/visulima/commit/0f48a96ed11d7339c62f3f147c7b2c8fcc605b03))
28
+ * **task-runner:** migrate .prettierrc.cjs to prettier.config.js ([cd1c045](https://github.com/visulima/visulima/commit/cd1c045e133f685a274924034ec70cf374edd5ba))
29
+
30
+ ### Build System
31
+
32
+ * regenerate NAPI-RS bindings as ESM ([f202caf](https://github.com/visulima/visulima/commit/f202caf3dc383a2ec24815c4935d8d68c29f33d0))
33
+ * switch NAPI-RS native builds to ESM output ([3d7cd61](https://github.com/visulima/visulima/commit/3d7cd615ad830392005915735c11771e0247ef3f))
34
+ * **task-runner:** move publish-native-addons to shared scripts/ ([73b5482](https://github.com/visulima/visulima/commit/73b5482e1ca0707aa8f191429deffbd7324a632d))
35
+
36
+
37
+ ### Dependencies
38
+
39
+ * **@visulima/humanizer:** upgraded to 3.0.0-alpha.9
40
+ * **@visulima/path:** upgraded to 3.0.0-alpha.8
41
+
1
42
  ## @visulima/task-runner [1.0.0-alpha.3](https://github.com/visulima/visulima/compare/@visulima/task-runner@1.0.0-alpha.2...@visulima/task-runner@1.0.0-alpha.3) (2026-03-26)
2
43
 
3
44
  ### Features
package/README.md CHANGED
@@ -27,11 +27,17 @@
27
27
 
28
28
  ## Features
29
29
 
30
+ - **Concurrent process runner**: Run multiple commands in parallel with native Rust performance (NAPI bindings) and JS fallback
31
+ - **Process tree management**: Proper cleanup via setsid/killpg (Unix) and Job Objects (Windows)
32
+ - **Command parser pipeline**: `npm:build` shortcuts, `npm run watch-*` wildcard expansion, `{1}` argument placeholders
33
+ - **Flow controllers**: Restart with backoff, stdin routing, timing summaries, teardown commands
34
+ - **npm script-shell support**: Honors `npm config set script-shell` for custom shells (Git Bash, etc.)
35
+ - **Long-running process support**: Configurable stdin mode (null/pipe/inherit), bounded output buffers
30
36
  - **Two caching modes**: Nx-style explicit inputs or Vite Task-style auto-fingerprinting
31
37
  - **Smart lockfile hashing**: Only hashes resolved versions relevant to each package (like Turborepo)
32
38
  - **Framework env inference**: Auto-detects Next.js, Vite, CRA, Gatsby, Nuxt, and more
33
39
  - **Remote caching**: Turborepo-compatible HTTP cache protocol
34
- - **Native Rust addon**: Optional xxHash-based parallel file hashing via napi-rs
40
+ - **Native Rust addon**: Parallel file hashing (xxHash), concurrent process management via tokio
35
41
  - **Dependency-aware scheduling**: Topological task ordering with priority-based batching
36
42
  - **Incremental file hashing**: mtime-based change detection for near-instant cache checks
37
43
  - **Affected detection**: Git diff-based filtering to only run tasks for changed packages
@@ -61,32 +67,144 @@ pnpm add @visulima/task-runner
61
67
  ```typescript
62
68
  import { defaultTaskRunner } from "@visulima/task-runner";
63
69
 
64
- const results = await defaultTaskRunner(tasks, {
65
- // Nx-style: explicit inputs
66
- namedInputs: {
67
- production: ["{projectRoot}/src/**/*"],
70
+ const results = await defaultTaskRunner(
71
+ tasks,
72
+ {
73
+ // Nx-style: explicit inputs
74
+ namedInputs: {
75
+ production: ["{projectRoot}/src/**/*"],
76
+ },
77
+ globalInputs: ["pnpm-lock.yaml", "tsconfig.base.json"],
78
+ globalEnv: ["NODE_ENV"],
79
+
80
+ // Or: auto-fingerprinting (Vite Task-style)
81
+ // autoFingerprint: true,
82
+
83
+ // Smart lockfile hashing (only bust cache for affected packages)
84
+ smartLockfileHashing: true,
85
+
86
+ // Auto-detect framework env vars (NEXT_PUBLIC_*, VITE_*, etc.)
87
+ frameworkInference: true,
88
+
89
+ // Remote cache (Turborepo-compatible)
90
+ remoteCache: {
91
+ url: "https://cache.example.com",
92
+ token: process.env.CACHE_TOKEN,
93
+ teamId: "my-team",
94
+ },
68
95
  },
69
- globalInputs: ["pnpm-lock.yaml", "tsconfig.base.json"],
70
- globalEnv: ["NODE_ENV"],
71
-
72
- // Or: auto-fingerprinting (Vite Task-style)
73
- // autoFingerprint: true,
96
+ context,
97
+ );
98
+ ```
74
99
 
75
- // Smart lockfile hashing (only bust cache for affected packages)
76
- smartLockfileHashing: true,
100
+ ## Concurrent Process Runner
77
101
 
78
- // Auto-detect framework env vars (NEXT_PUBLIC_*, VITE_*, etc.)
79
- frameworkInference: true,
102
+ Run multiple commands in parallel with real-time output streaming, process tree management, and automatic native acceleration.
80
103
 
81
- // Remote cache (Turborepo-compatible)
82
- remoteCache: {
83
- url: "https://cache.example.com",
84
- token: process.env.CACHE_TOKEN,
85
- teamId: "my-team",
104
+ ```typescript
105
+ import { runConcurrently } from "@visulima/task-runner";
106
+
107
+ // Basic usage
108
+ const result = await runConcurrently(["npm run build", "npm run test", "npm run lint"]);
109
+ console.log(result.success ? "All passed" : "Some failed");
110
+
111
+ // With options
112
+ const result = await runConcurrently(
113
+ [
114
+ { command: "vite dev", name: "web", stdin: "inherit" },
115
+ { command: "node api.js", name: "api" },
116
+ ],
117
+ {
118
+ maxProcesses: 4,
119
+ killOthers: ["failure"], // Kill all if one fails
120
+ successCondition: "all", // All must exit 0
121
+ onEvent: (event) => {
122
+ // Real-time streaming
123
+ if (event.kind === "stdout") {
124
+ console.log(`[${event.index}] ${event.text}`);
125
+ }
126
+ },
86
127
  },
87
- }, context);
128
+ );
129
+ ```
130
+
131
+ ### Command Parser
132
+
133
+ Use `parseCommands` to expand shortcuts and wildcards before passing to `runConcurrently`:
134
+
135
+ ```typescript
136
+ import { parseCommands, runConcurrently } from "@visulima/task-runner";
137
+
138
+ const commands = parseCommands([
139
+ "npm:build", // -> npm run build
140
+ "pnpm:test", // -> pnpm run test
141
+ '"quoted command"', // -> quoted command (quotes stripped)
142
+ "npm run watch-*", // -> expands to all matching scripts in package.json
143
+ "deno task dev-*", // -> expands from deno.json/deno.jsonc tasks
144
+ ]);
145
+
146
+ await runConcurrently(commands);
147
+ ```
148
+
149
+ ### Flow Controllers
150
+
151
+ ```typescript
152
+ import { runConcurrently } from "@visulima/task-runner";
153
+
154
+ // Restart failed commands with exponential backoff
155
+ await runConcurrently(["flaky-command"], {
156
+ restart: { tries: 3, delay: "exponential" },
157
+ });
158
+
159
+ // Print timing summary after completion
160
+ await runConcurrently(["npm run build", "npm run test"], {
161
+ timings: true,
162
+ });
163
+
164
+ // Run cleanup commands after all processes finish
165
+ await runConcurrently(["npm run dev"], {
166
+ teardown: ["docker compose down", "rm -rf .cache"],
167
+ });
88
168
  ```
89
169
 
170
+ ### Shell Configuration
171
+
172
+ The runner automatically detects `npm config set script-shell` for custom shells (e.g., Git Bash on Windows):
173
+
174
+ ```typescript
175
+ import { runConcurrently, detectScriptShell } from "@visulima/task-runner";
176
+
177
+ // Auto-detected from npm config
178
+ await runConcurrently(["echo hello"]);
179
+
180
+ // Or override explicitly
181
+ await runConcurrently(["echo hello"], {
182
+ shellPath: "/usr/bin/bash",
183
+ });
184
+ ```
185
+
186
+ ### Stdin Modes
187
+
188
+ For long-running processes like dev servers:
189
+
190
+ ```typescript
191
+ await runConcurrently([
192
+ { command: "vite dev", stdin: "inherit" }, // Child reads terminal directly
193
+ { command: "node worker.js", stdin: "null" }, // No stdin (default)
194
+ { command: "node repl.js", stdin: "pipe" }, // Programmatic stdin access
195
+ ]);
196
+ ```
197
+
198
+ ### Native vs Fallback
199
+
200
+ The runner automatically uses the Rust NAPI addon when available for:
201
+
202
+ - Process tree killing via setsid/killpg (Unix) and Job Objects (Windows)
203
+ - Async I/O multiplexing via tokio
204
+ - Signal propagation (SIGINT/SIGTERM/SIGHUP)
205
+
206
+ Falls back to a pure JavaScript implementation when the native addon is not compiled.
207
+
90
208
  ## Caching Modes
91
209
 
92
210
  ### Nx-style (explicit inputs)
@@ -94,19 +212,19 @@ const results = await defaultTaskRunner(tasks, {
94
212
  Declare which files, env vars, and runtime values should be included in the cache hash:
95
213
 
96
214
  ```typescript
97
- const results = await defaultTaskRunner(tasks, {
98
- namedInputs: {
99
- production: [
100
- "{projectRoot}/src/**/*",
101
- { env: "NODE_ENV" },
102
- { runtime: "node --version" },
103
- ],
104
- },
105
- targetDefaults: {
106
- build: { inputs: ["production"] },
107
- test: { inputs: ["production", "{projectRoot}/**/*.test.ts"] },
215
+ const results = await defaultTaskRunner(
216
+ tasks,
217
+ {
218
+ namedInputs: {
219
+ production: ["{projectRoot}/src/**/*", { env: "NODE_ENV" }, { runtime: "node --version" }],
220
+ },
221
+ targetDefaults: {
222
+ build: { inputs: ["production"] },
223
+ test: { inputs: ["production", "{projectRoot}/**/*.test.ts"] },
224
+ },
108
225
  },
109
- }, context);
226
+ context,
227
+ );
110
228
  ```
111
229
 
112
230
  ### Auto-fingerprint (Vite Task-style)
@@ -114,11 +232,15 @@ const results = await defaultTaskRunner(tasks, {
114
232
  Automatically tracks which files a task accesses during execution:
115
233
 
116
234
  ```typescript
117
- const results = await defaultTaskRunner(tasks, {
118
- autoFingerprint: true,
119
- fingerprintEnvPatterns: ["VITE_*", "NODE_ENV"],
120
- cacheDiagnostics: true, // Shows why cache misses occur
121
- }, context);
235
+ const results = await defaultTaskRunner(
236
+ tasks,
237
+ {
238
+ autoFingerprint: true,
239
+ fingerprintEnvPatterns: ["VITE_*", "NODE_ENV"],
240
+ cacheDiagnostics: true, // Shows why cache misses occur
241
+ },
242
+ context,
243
+ );
122
244
  ```
123
245
 
124
246
  ## API
@@ -127,27 +249,47 @@ const results = await defaultTaskRunner(tasks, {
127
249
 
128
250
  The main entry point. Runs tasks with caching, scheduling, and lifecycle support.
129
251
 
252
+ ### `runConcurrently(commands, options?)`
253
+
254
+ Run commands concurrently with process management and output streaming.
255
+
256
+ | Option | Type | Description |
257
+ | ------------------ | ---------------------------- | --------------------------------------------------- |
258
+ | `maxProcesses` | `number` | Max simultaneous processes (0 = unlimited) |
259
+ | `killOthers` | `("failure" \| "success")[]` | Kill others when a process exits |
260
+ | `killSignal` | `string` | Signal for killing (default: "SIGTERM") |
261
+ | `killTimeout` | `number` | Ms before SIGKILL after kill signal (default: 5000) |
262
+ | `successCondition` | `string` | "all", "first", "last", "command-\<name\>" |
263
+ | `shellPath` | `string` | Custom shell path (auto-detected from npm config) |
264
+ | `restart` | `{ tries, delay }` | Restart failed commands with backoff |
265
+ | `teardown` | `string[]` | Cleanup commands to run after completion |
266
+ | `timings` | `boolean` | Print timing summary table |
267
+ | `onEvent` | `(event) => void` | Real-time stdout/stderr/close/error events |
268
+
130
269
  ### Key Options
131
270
 
132
- | Option | Type | Description |
133
- |--------|------|-------------|
134
- | `parallel` | `number \| boolean` | Max parallel tasks (default: 3) |
135
- | `smartLockfileHashing` | `boolean` | Hash only relevant lockfile entries per package |
136
- | `frameworkInference` | `boolean` | Auto-detect framework env var prefixes |
137
- | `autoFingerprint` | `boolean` | Enable Vite Task-style auto-fingerprinting |
138
- | `globalInputs` | `string[]` | Files that invalidate all caches when changed |
139
- | `globalEnv` | `string[]` | Env vars that invalidate all caches |
140
- | `remoteCache` | `object` | Remote cache server configuration |
141
- | `dryRun` | `boolean` | Compute hashes without executing |
142
- | `summarize` | `boolean` | Generate JSON run summary |
143
- | `cacheDiagnostics` | `boolean` | Log cache miss reasons |
144
- | `maxCacheSize` | `string` | Max cache size (e.g., "1GB") |
145
- | `maxCacheAge` | `number` | Max cache entry age in ms |
271
+ | Option | Type | Description |
272
+ | ---------------------- | ------------------- | ----------------------------------------------- |
273
+ | `parallel` | `number \| boolean` | Max parallel tasks (default: 3) |
274
+ | `smartLockfileHashing` | `boolean` | Hash only relevant lockfile entries per package |
275
+ | `frameworkInference` | `boolean` | Auto-detect framework env var prefixes |
276
+ | `autoFingerprint` | `boolean` | Enable Vite Task-style auto-fingerprinting |
277
+ | `globalInputs` | `string[]` | Files that invalidate all caches when changed |
278
+ | `globalEnv` | `string[]` | Env vars that invalidate all caches |
279
+ | `remoteCache` | `object` | Remote cache server configuration |
280
+ | `dryRun` | `boolean` | Compute hashes without executing |
281
+ | `summarize` | `boolean` | Generate JSON run summary |
282
+ | `cacheDiagnostics` | `boolean` | Log cache miss reasons |
283
+ | `maxCacheSize` | `string` | Max cache size (e.g., "1GB") |
284
+ | `maxCacheAge` | `number` | Max cache entry age in ms |
146
285
 
147
286
  ### Exports
148
287
 
149
288
  The package exports many building blocks for custom task runners:
150
289
 
290
+ - **Concurrent Runner**: `runConcurrently`, `runConcurrentFallback`, `detectScriptShell`
291
+ - **Command Parser**: `parseCommands`, `expandShortcut`, `expandWildcard`, `expandArguments`, `stripQuotes`
292
+ - **Flow Controllers**: `withRestart`, `createInputHandler`, `logTimings`, `formatTimingTable`, `runTeardown`
151
293
  - **Task Graph**: `createTaskGraph`, `findCycle`, `walkTaskGraph`, `makeAcyclic`
152
294
  - **Hashing**: `InProcessTaskHasher`, `IncrementalFileHasher`, `computeTaskHash`
153
295
  - **Caching**: `Cache`, `RemoteCache`, `FingerprintManager`
@@ -1,16 +1,26 @@
1
- import type { ProjectConfiguration, ProjectGraph } from "./types.d.ts";
1
+ import type { AffectedScope, ProjectConfiguration, ProjectGraph } from "./types.d.ts";
2
2
  /**
3
3
  * Options for determining affected projects.
4
4
  */
5
5
  interface AffectedOptions {
6
6
  /** The base ref to compare against (default: "main") */
7
7
  base?: string;
8
+ /**
9
+ * Control how far downstream (dependents of changed projects) to include.
10
+ * @default "deep"
11
+ */
12
+ downstream?: AffectedScope;
8
13
  /** The head ref to compare (default: "HEAD") */
9
14
  head?: string;
10
15
  /** Project graph for dependency resolution */
11
16
  projectGraph: ProjectGraph;
12
17
  /** All project configurations keyed by name */
13
18
  projects: Record<string, ProjectConfiguration>;
19
+ /**
20
+ * Control how far upstream (dependencies of changed projects) to include.
21
+ * @default "none"
22
+ */
23
+ upstream?: AffectedScope;
14
24
  /** The workspace root directory */
15
25
  workspaceRoot: string;
16
26
  }
@@ -18,13 +28,37 @@ interface AffectedOptions {
18
28
  * Result of affected detection.
19
29
  */
20
30
  interface AffectedResult {
21
- /** Projects affected by changes (including transitive dependents) */
31
+ /** All affected projects (union of changed, downstream, and upstream) */
22
32
  affectedProjects: string[];
23
33
  /** Files that changed between base and head */
24
34
  changedFiles: string[];
25
35
  /** Projects that were directly changed */
26
36
  changedProjects: string[];
37
+ /** Projects affected because they depend on changed projects */
38
+ downstreamProjects: string[];
39
+ /** Projects that changed projects depend on */
40
+ upstreamProjects: string[];
27
41
  }
42
+ /**
43
+ * Builds a map from each project to the set of projects that depend on it (reverse/downstream).
44
+ */
45
+ declare const buildReverseDependencyMap: (projectGraph: ProjectGraph) => Map<string, Set<string>>;
46
+ /**
47
+ * Builds a map from each project to the set of projects it depends on (forward/upstream).
48
+ */
49
+ declare const buildForwardDependencyMap: (projectGraph: ProjectGraph) => Map<string, Set<string>>;
50
+ /**
51
+ * Expands a set of changed projects based on upstream/downstream scope settings.
52
+ * Returns a new set containing all affected projects.
53
+ */
54
+ declare const expandAffected: (changedProjects: Set<string>, projectGraph: ProjectGraph, options: {
55
+ downstream: AffectedScope;
56
+ upstream: AffectedScope;
57
+ }) => {
58
+ affected: Set<string>;
59
+ downstream: Set<string>;
60
+ upstream: Set<string>;
61
+ };
28
62
  /**
29
63
  * Gets the list of files changed between two git refs.
30
64
  * Uses execFile with argument arrays to prevent command injection.
@@ -45,4 +79,4 @@ declare const getAffectedProjects: (options: AffectedOptions) => Promise<Affecte
45
79
  */
46
80
  declare const filterAffectedTasks: (taskIds: string[], affectedProjects: Set<string>) => string[];
47
81
  export type { AffectedOptions, AffectedResult };
48
- export { filterAffectedTasks, getAffectedProjects, getChangedFiles };
82
+ export { buildForwardDependencyMap, buildReverseDependencyMap, expandAffected, filterAffectedTasks, getAffectedProjects, getChangedFiles };
@@ -0,0 +1,11 @@
1
+ import type { ConcurrentCommandConfig } from "../types.d.ts";
2
+ /**
3
+ * Expands argument placeholders in command strings.
4
+ *
5
+ * Placeholders:
6
+ * {1}, {2}, ... -> specific positional argument
7
+ * {@} -> all arguments, individually quoted
8
+ * {*} -> all arguments as a single quoted string
9
+ * \{1} -> literal {1} (escaped)
10
+ */
11
+ export declare const expandArguments: (config: ConcurrentCommandConfig, additionalArguments: string[]) => ConcurrentCommandConfig;
@@ -0,0 +1,15 @@
1
+ import type { ConcurrentCommandConfig } from "../types.d.ts";
2
+ /**
3
+ * Expands package manager shortcuts into full commands.
4
+ *
5
+ * This parser transforms shorthand notation into proper package manager
6
+ * invocations. No user input is involved -- command strings originate
7
+ * from the calling code which reads package.json scripts.
8
+ *
9
+ * Examples:
10
+ * npm:build -> npm run build
11
+ * pnpm:test -> pnpm run test
12
+ * node:script -> node --run script
13
+ * deno:task -> deno task task
14
+ */
15
+ export declare const expandShortcut: (config: ConcurrentCommandConfig) => ConcurrentCommandConfig;
@@ -0,0 +1,13 @@
1
+ import type { ConcurrentCommandConfig } from "../types.d.ts";
2
+ /**
3
+ * Expands wildcard patterns in package manager "run" commands.
4
+ *
5
+ * Reads scripts from package.json and matches against the wildcard pattern.
6
+ * Returns one ConcurrentCommandConfig per matching script.
7
+ *
8
+ * Example: "npm run watch-*" with scripts { "watch-js": "...", "watch-css": "..." }
9
+ * -> ["npm run watch-js", "npm run watch-css"]
10
+ *
11
+ * No user input is involved -- patterns come from the calling code.
12
+ */
13
+ export declare const expandWildcard: (config: ConcurrentCommandConfig) => ConcurrentCommandConfig | ConcurrentCommandConfig[];
@@ -0,0 +1,18 @@
1
+ import type { ConcurrentCommandConfig, ConcurrentCommandInput } from "../types.d.ts";
2
+ export interface ParseCommandsOptions {
3
+ /** Additional arguments for placeholder expansion ({1}, {@}, {*}). */
4
+ additionalArguments?: string[];
5
+ }
6
+ /**
7
+ * Parse and expand command inputs through the full pipeline:
8
+ * 1. Normalize string inputs to config objects
9
+ * 2. Strip surrounding quotes
10
+ * 3. Expand package manager shortcuts (npm:build -> npm run build)
11
+ * 4. Expand wildcard patterns (npm run watch-* -> multiple commands)
12
+ * 5. Expand argument placeholders ({1}, {@}, {*})
13
+ */
14
+ export declare const parseCommands: (inputs: ConcurrentCommandInput[], options?: ParseCommandsOptions) => ConcurrentCommandConfig[];
15
+ export { expandArguments } from "./expand-arguments.d.ts";
16
+ export { expandShortcut } from "./expand-shortcut.d.ts";
17
+ export { expandWildcard } from "./expand-wildcard.d.ts";
18
+ export { stripQuotes } from "./strip-quotes.d.ts";
@@ -0,0 +1,6 @@
1
+ import type { ConcurrentCommandConfig } from "../types.d.ts";
2
+ /**
3
+ * Removes surrounding quotes from a command string.
4
+ * Handles both single and double quotes.
5
+ */
6
+ export declare const stripQuotes: (config: ConcurrentCommandConfig) => ConcurrentCommandConfig;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Pure JavaScript fallback for the concurrent process runner.
3
+ * Used when the native Rust addon is not available.
4
+ *
5
+ * SECURITY NOTE: Commands executed here originate from package.json scripts
6
+ * (trusted input, not user-supplied). Shell execution via spawn() with
7
+ * explicit shell binary (sh -c / cmd.exe /s /c) is intentional -- package
8
+ * scripts require shell features (pipes, redirects, env expansion).
9
+ * This is the same approach used by npm/pnpm/yarn themselves.
10
+ */
11
+ import type { ConcurrentCommandConfig, ConcurrentRunnerOptions, ConcurrentRunResult } from "./types.d.ts";
12
+ /**
13
+ * Run commands concurrently using pure JavaScript (child_process.spawn).
14
+ * This is the fallback when the native Rust addon is unavailable.
15
+ */
16
+ export declare const runConcurrentFallback: (commands: ConcurrentCommandConfig[], options: ConcurrentRunnerOptions) => Promise<ConcurrentRunResult>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Public API for the concurrent process runner.
3
+ *
4
+ * Uses the native Rust addon when available, falling back to
5
+ * a pure JavaScript implementation. Integrates flow controllers
6
+ * (restart, teardown, timings) around the core runner.
7
+ */
8
+ import type { ConcurrentCommandInput, ConcurrentRunnerOptions, ConcurrentRunResult } from "./types.d.ts";
9
+ /**
10
+ * Run commands concurrently with output streaming and process management.
11
+ *
12
+ * Automatically uses the native Rust addon for performance when available,
13
+ * falling back to a pure JavaScript implementation.
14
+ *
15
+ * Supports flow controllers:
16
+ * - `restart`: retry failed commands with configurable delay/backoff
17
+ * - `teardown`: run cleanup commands after all processes complete
18
+ * - `timings`: print a timing summary table
19
+ * @param commands Array of command strings or config objects
20
+ * @param options Runner options (maxProcesses, killOthers, restart, teardown, etc.)
21
+ * @returns Promise resolving to the run result with close events and success status
22
+ */
23
+ export declare const runConcurrently: (commands: ConcurrentCommandInput[], options?: ConcurrentRunnerOptions) => Promise<ConcurrentRunResult>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Detects the configured script shell for npm/pnpm/yarn.
3
+ *
4
+ * Resolution order:
5
+ * 1. `npm_config_script_shell` env var (set by npm when running scripts)
6
+ * 2. `npm config get script-shell` subprocess (cached after first call)
7
+ * 3. Platform default (undefined = let the runner use /bin/sh or cmd.exe)
8
+ */
9
+ /**
10
+ * Detect the npm script-shell configuration.
11
+ *
12
+ * Returns the shell path if configured, or undefined to use platform defaults.
13
+ * The result is cached after the first call.
14
+ */
15
+ export declare const detectScriptShell: () => string | undefined;
16
+ /**
17
+ * Reset the cached shell path. Useful for testing.
18
+ */
19
+ export declare const resetShellCache: () => void;
@@ -0,0 +1,7 @@
1
+ export type { InputHandlerOptions } from "./input-handler.d.ts";
2
+ export { createInputHandler } from "./input-handler.d.ts";
3
+ export { formatTimingTable, logTimings } from "./log-timings.d.ts";
4
+ export type { RestartOptions } from "./restart-process.d.ts";
5
+ export { withRestart } from "./restart-process.d.ts";
6
+ export type { TeardownOptions } from "./teardown.d.ts";
7
+ export { runTeardown } from "./teardown.d.ts";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Input handler flow controller.
3
+ *
4
+ * Routes stdin data to specific child processes based on a prefix pattern.
5
+ * Input prefixed with "name:" or "index:" is routed to that command's stdin.
6
+ * Unprefixed input goes to the default target (index 0).
7
+ *
8
+ * NOTE: This module does NOT execute commands -- it only pipes user-typed
9
+ * stdin data to already-running child process stdin streams. No shell
10
+ * invocation or command injection risk.
11
+ *
12
+ * LIMITATION: This is a standalone utility -- it is NOT integrated into
13
+ * runConcurrently() because the concurrent runner does not expose child
14
+ * stdin streams. To use this, spawn processes manually with stdin: "pipe"
15
+ * and pass the writable streams to createInputHandler().
16
+ *
17
+ * TODO: To fully integrate stdin routing into runConcurrently(), we would need:
18
+ * 1. A new "started" ProcessEvent that carries a writable stdin reference
19
+ * 2. On the Rust side: expose tokio::process::ChildStdin through NAPI
20
+ * (requires a custom wrapper since ChildStdin can't cross FFI directly)
21
+ * 3. In the JS fallback: return child.stdin from spawnCommand()
22
+ */
23
+ import type { Readable, Writable } from "node:stream";
24
+ export interface InputHandlerOptions {
25
+ /** Default command index to route unprefixed input to. Default: 0. */
26
+ defaultTarget?: number;
27
+ /** Stream to read input from. Default: process.stdin. */
28
+ inputStream?: Readable;
29
+ /** Whether to pause the input stream when all processes finish. Default: true. */
30
+ pauseOnFinish?: boolean;
31
+ }
32
+ interface CommandStdin {
33
+ index: number;
34
+ name?: string;
35
+ stdin: Writable;
36
+ }
37
+ /**
38
+ * Creates an input handler that routes stdin to child processes.
39
+ * @param commands Map of command index/name to their stdin streams
40
+ * @param options Input handler configuration
41
+ * @returns cleanup function to call when done
42
+ */
43
+ export declare const createInputHandler: (commands: CommandStdin[], options?: InputHandlerOptions) => () => void;
44
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Log timings flow controller.
3
+ *
4
+ * Prints a summary table of all command durations after completion.
5
+ */
6
+ import type { ConcurrentCloseEvent } from "../types.d.ts";
7
+ /**
8
+ * Generate a timing summary table string from close events.
9
+ * @param closeEvents Close events from the concurrent run (in completion order)
10
+ * @returns Formatted table string
11
+ */
12
+ export declare const formatTimingTable: (closeEvents: ConcurrentCloseEvent[]) => string;
13
+ /**
14
+ * Print timing summary to a writable stream.
15
+ * @param closeEvents Close events from the concurrent run
16
+ * @param output Output stream (default: process.stdout)
17
+ */
18
+ export declare const logTimings: (closeEvents: ConcurrentCloseEvent[], output?: NodeJS.WritableStream) => void;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Restart flow controller.
3
+ *
4
+ * Re-runs failed commands with configurable retry count and delay.
5
+ * Supports fixed delay or exponential backoff.
6
+ */
7
+ import type { ConcurrentCommandConfig, ConcurrentRunnerOptions, ConcurrentRunResult } from "../types.d.ts";
8
+ export interface RestartOptions {
9
+ /** Delay between restarts in milliseconds. "exponential" for 2^attempt * 1000ms. */
10
+ delay: number | "exponential";
11
+ /** Maximum number of restart attempts per command. 0 = no restarts. -1 = infinite. */
12
+ tries: number;
13
+ }
14
+ /**
15
+ * Wraps a runner function to add restart-on-failure behavior.
16
+ * @param runFn The underlying runner function (runConcurrently or runConcurrentFallback)
17
+ * @param commands The original command configs
18
+ * @param options Runner options
19
+ * @param restartOptions Restart-specific options
20
+ */
21
+ export declare const withRestart: (runFunction: (commands: ConcurrentCommandConfig[], options: ConcurrentRunnerOptions) => Promise<ConcurrentRunResult>, commands: ConcurrentCommandConfig[], options: ConcurrentRunnerOptions, restartOptions: RestartOptions) => Promise<ConcurrentRunResult>;