bun-workspaces 1.8.2 → 1.10.0

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 (47) hide show
  1. package/AGENTS.md +537 -0
  2. package/README.md +51 -13
  3. package/package.json +1 -1
  4. package/src/2392.mjs +184 -3
  5. package/src/5166.mjs +1 -0
  6. package/src/8529.mjs +10 -0
  7. package/src/affected/affectedBaseRef.mjs +12 -0
  8. package/src/affected/externalDependencyChanges.mjs +47 -0
  9. package/src/affected/fileAffectedWorkspaces.mjs +152 -54
  10. package/src/affected/gitAffectedFiles.mjs +44 -1
  11. package/src/affected/gitAffectedWorkspaces.mjs +73 -3
  12. package/src/affected/index.mjs +2 -0
  13. package/src/ai/mcp/serverState.mjs +1 -1
  14. package/src/cli/commands/commandHandlerUtils.mjs +12 -7
  15. package/src/cli/commands/commands.mjs +4 -1
  16. package/src/cli/commands/handleSimpleCommands.mjs +2 -2
  17. package/src/cli/commands/listAffected.mjs +184 -0
  18. package/src/cli/commands/runScript/handleRunAffected.mjs +99 -0
  19. package/src/cli/commands/runScript/handleRunScript.mjs +19 -202
  20. package/src/cli/commands/runScript/index.mjs +1 -0
  21. package/src/cli/commands/runScript/scriptRunFlow.mjs +213 -0
  22. package/src/cli/index.d.ts +749 -134
  23. package/src/config/public.d.ts +66 -2
  24. package/src/config/rootConfig/rootConfig.mjs +9 -9
  25. package/src/config/rootConfig/rootConfigSchema.mjs +3 -0
  26. package/src/config/workspaceConfig/mergeWorkspaceConfig.mjs +33 -19
  27. package/src/config/workspaceConfig/workspaceConfig.mjs +3 -0
  28. package/src/config/workspaceConfig/workspaceConfigSchema.mjs +26 -0
  29. package/src/index.d.ts +307 -5
  30. package/src/index.mjs +1 -0
  31. package/src/internal/bun/bunLock.mjs +33 -0
  32. package/src/internal/generated/aiDocs/docs.mjs +169 -9
  33. package/src/internal/generated/ajv/validateRootConfig.mjs +1 -1
  34. package/src/internal/generated/ajv/validateWorkspaceConfig.mjs +1 -1
  35. package/src/project/implementations/fileSystemProject/affectedWorkspaces.mjs +227 -0
  36. package/src/project/implementations/{fileSystemProject.mjs → fileSystemProject/fileSystemProject.mjs} +169 -12
  37. package/src/project/implementations/fileSystemProject/index.mjs +4 -0
  38. package/src/project/implementations/memoryProject.mjs +1 -0
  39. package/src/project/implementations/projectBase.mjs +11 -17
  40. package/src/project/index.mjs +1 -1
  41. package/src/rslib-runtime.mjs +0 -31
  42. package/src/workspaces/applyWorkspacePatternConfigs.mjs +16 -2
  43. package/src/workspaces/dependencyGraph/resolveDependencies.mjs +68 -18
  44. package/src/workspaces/dependencyGraph/validateDependencyRules.mjs +14 -7
  45. package/src/workspaces/findWorkspaces.mjs +3 -0
  46. package/src/workspaces/workspace.mjs +8 -2
  47. package/src/workspaces/workspacePattern.mjs +134 -46
package/AGENTS.md ADDED
@@ -0,0 +1,537 @@
1
+ <!-- bun-workspaces (npm package) agent documentation begin -->
2
+ ## Project Overview
3
+
4
+ bun-workspaces is a CLI and TypeScript API to help manage Bun monorepos. It reads `bun.lock` to find all workspaces in the project. It is referred to as "bw" for short, which is also the recommended CLI alias. The overall goal is a monorepo tool that is more lightweight than others, with still powerful comparable features, requiring no special config to get started, only a standard Bun repo using workspaces.
5
+
6
+ Three main domain terms to know:
7
+
8
+ - Project: generally represents a monorepo and is defined by the root `package.json` file
9
+ - Workspace: a nested package within a project. The root package.json can count as a workspace as well, but by default, only nested packages are considered workspaces.
10
+ - Script: an entry in the `scripts` field of a workspace's `package.json` file. bw can also run one-off commands known as "inline scripts," which can use the Bun shell or system shell (`sh -c` or `cmd /d /s /c` for windows).
11
+
12
+ bw also supports **affected workspace** detection: given a set of changed files (from a git diff or an explicit list), it determines which workspaces are meaningfully changed. This drives `bw list-affected`/`bw run-affected` for orchestrating builds, tests, etc. across only the workspaces that need them.
13
+
14
+ ## Concepts
15
+
16
+ ### Workspace patterns
17
+
18
+ Many features accept a list of workspace patterns to match a subset of workspaces:
19
+
20
+ `[not:][(name|alias|path|tag):][re:]<value>`
21
+
22
+ By default, a pattern matches the workspace name or alias: `my-workspace-name` or `my-alias-name`. Aliases are defined in config explained below.
23
+
24
+ Patterns can include a wildcard to match only by workspace name: `my-workspace-*`.
25
+
26
+ - Alias pattern specifier: `alias:my-alias-*`.
27
+ - Path pattern specifier (supports glob): `path:packages/**/*`.
28
+ - Name pattern specifier: `name:my-workspace-*`.
29
+ - Tag pattern specifier: `tag:my-tag`.
30
+ - Any pattern can start with `not:` to negate the pattern. (e.g. "not:my-workspace-name", "not:tag:my-tag-\*") This excludes workspaces that match any other present patterns from a result.
31
+ - Regex pattern modifier can be applied before the pattern value: `re:` (e.g. "re:^my-workspace-.+" or "not:alias:re:^my-alias-.+")
32
+
33
+ #### Special selectors
34
+
35
+ - Special root workspace selector: `@root`. This is a reference to the root workspace, whether it's included in a Project's workspace list or not.
36
+
37
+ ### Workspace Script Metadata
38
+
39
+ Scripts ran via bun-workspaces can access metadata about the workspace, script, and project
40
+ via env vars. This same metadata can also be interpolated into inline scripts and appended args.
41
+
42
+ ```typescript
43
+ // in a workspace's script invoked by bun-workspaces using a metadata function
44
+ import { getWorkspaceScriptMetadata } from "bun-workspaces/script";
45
+
46
+ // Use the helper within a script that was invoked via bun-workspaces
47
+ const projectPath = getWorkspaceScriptMetadata("projectPath");
48
+ const projectName = getWorkspaceScriptMetadata("projectName");
49
+ const workspaceName = getWorkspaceScriptMetadata("workspaceName");
50
+ const workspacePath = getWorkspaceScriptMetadata("workspacePath");
51
+ const workspaceRelativePath = getWorkspaceScriptMetadata(
52
+ "workspaceRelativePath",
53
+ );
54
+ const scriptName = getWorkspaceScriptMetadata("scriptName");
55
+ ```
56
+
57
+ ```typescript
58
+ // In a script, but accessing the same data via plain environment variables (same values as previous example)
59
+ const projectPath = process.env.BW_PROJECT_PATH;
60
+ const workspaceName = process.env.BW_WORKSPACE_NAME;
61
+ const workspacePath = process.env.BW_WORKSPACE_PATH;
62
+ const workspaceRelativePath = process.env.BW_WORKSPACE_RELATIVE_PATH;
63
+ const scriptName = process.env.BW_SCRIPT_NAME;
64
+ ```
65
+
66
+ ```bash
67
+ # interpolated
68
+ bw run "bun <projectPath>/my-script.ts" --inline \
69
+ --inline-name="my-script-name" \
70
+ --args="<workspaceName> <workspacePath>"
71
+ ```
72
+
73
+ ### Affected workspaces
74
+
75
+ A workspace is "affected" when something in its set of **inputs** has changed. Inputs default to:
76
+
77
+ - Files in the workspace's directory (only git-trackable files; the default file pattern is `"."`)
78
+ - Workspace dependencies — if a workspace dep is affected for any reason, dependents cascade as affected
79
+ - All non-workspace dependencies declared in its `package.json` (across all four maps: `dependencies`, `devDependencies`, `peerDependencies`, `optionalDependencies`). Version changes are detected by diffing resolved versions in `bun.lock`. For `peerDependencies`/`optionalDependencies`, lockfile presence is the gate — an unresolved optional (e.g., a platform-skipped native binding) emits no change.
80
+
81
+ Inputs are configurable per workspace (`defaultInputs`) and per script (`scripts[name].inputs`):
82
+
83
+ - `files`: file/dir/glob patterns relative to the workspace. Leading `/` makes a pattern relative to the project root. Prefix `!` to exclude. Only git-trackable files match.
84
+ - `workspacePatterns`: workspace patterns whose matched workspaces are treated as inputs (like dependencies, but without needing a real `package.json` dep).
85
+ - `externalDependencies`: an allowlist of package names. Omitted = all external deps participate; `[]` = none participate; non-empty = only listed names participate (intersected with the workspace's actual external deps).
86
+
87
+ There are two diff sources:
88
+
89
+ - **git** (default): diff `HEAD` against the configured base ref (default `main`, configurable via `affectedBaseRef` in the root config or `BW_AFFECTED_BASE_REF_DEFAULT` env var). Uncommitted changes (staged, unstaged, untracked) are included by default. Gitignored files never participate.
90
+ - **fileList**: pass changed files explicitly (paths, dirs, or globs) — bypasses git entirely.
91
+
92
+ Use `--explain` for a per-workspace summary of changed inputs and dep cascade reasons, and `--explain --detailed` for full per-file/edge breakdowns including the affected-dep chain.
93
+
94
+ ### CLI examples:
95
+
96
+ ```bash
97
+ alias bw="bunx bun-workspaces"
98
+
99
+ bw list-workspaces # human-readable output
100
+ bw ls --json --pretty # ls is alias for list-workspaces
101
+ bw ls "name:my-workspace-*" "alias:my-alias-*" "path:packages/**/*" # accepts workspace patterns
102
+
103
+ # info includes the name, aliases, path, etc.
104
+ bw workspace-info my-workspace
105
+ bw info my-workspace --json --pretty # info is alias for workspace-info
106
+
107
+ # info includes the script name and workspaces that have it in their package.json "scripts" field
108
+ bw script-info my-script --json --pretty
109
+
110
+ # run the package.json "lint" script for all workspaces that have it
111
+ bw run-script lint
112
+
113
+ # run is alias for run-script
114
+ # run the package.json "lint" script for workspaces using matching specifiers
115
+ bw run lint my-workspace-name "alias:my-alias-pattern-*" "path:my-glob/**/*" # accepts workspace patterns
116
+
117
+ # A workspace's script will wait until any workspaces it depends on have completed
118
+ # Similar to Bun's --filter behavior
119
+ bw run lint --dep-order
120
+
121
+ # Continue running scripts even if a dependency fails
122
+ bw run lint --dep-order --ignore-dep-failure
123
+
124
+ # special root workspace selector (works even if root workspace is not included)
125
+ bw run lint @root
126
+
127
+ # Scripts run in parallel by default
128
+ bw run lint --parallel=false # Run in series
129
+
130
+ # Default can be overridden by config or env var BW_PARALLEL_MAX_DEFAULT
131
+ bw run lint --parallel # default "auto", os.availableParallelism()
132
+ bw run lint --parallel=2 # Run in parallel with a max of 2 concurrent scripts
133
+ bw run lint --parallel=50% # 50% of os.availableParallelism()
134
+ bw run lint --parallel=unbounded # run all in one batch
135
+
136
+ # add args to the script command
137
+ bw run lint --args="--my-arg=value"
138
+ bw run lint --args="--my-arg=<workspaceName>" # use the workspace name in args
139
+
140
+ # run the script as an inline command from the workspace directory
141
+ bw run "bun build" --inline
142
+ bw run "bun build" --inline --inline-name="my-script"
143
+ bw run "bun build" --inline --shell=system # use the system shell
144
+
145
+ # Use the grouped output style (default when on a TTY)
146
+ bw run my-script --output-style=grouped
147
+
148
+ # Set the max preview lines for script output in grouped output style
149
+ bw run my-script --output-style=grouped --grouped-lines=auto
150
+ bw run my-script --output-style=grouped --grouped-lines=10
151
+
152
+ # Use simple script output with workspace prefixes (default when not on a TTY)
153
+ bw run my-script --output-style=prefixed
154
+
155
+ # Use the plain output style (no workspace prefixes)
156
+ bw run my-script --output-style=plain
157
+
158
+ # List affected workspaces (default: git diff HEAD vs the configured base ref, "main" by default)
159
+ bw list-affected
160
+ bw ls-affected # alias
161
+
162
+ # Compare specific git refs
163
+ bw ls-affected --base=my-branch-a --head=my-branch-b
164
+ bw ls-affected -B my-branch-a -H my-branch-b # short forms
165
+
166
+ # Resolve inputs for a specific script (uses scripts[name].inputs when configured)
167
+ bw ls-affected --script=build
168
+
169
+ # Ignore some uncommitted changes (uncommitted included by default)
170
+ bw ls-affected --ignore-uncommitted # all of: staged, unstaged, untracked
171
+ bw ls-affected --ignore-untracked
172
+ bw ls-affected --ignore-unstaged
173
+ bw ls-affected --ignore-staged
174
+
175
+ # Skip workspace dep cascade (only direct file/external-dep changes flag a workspace)
176
+ bw ls-affected --ignore-workspace-deps
177
+
178
+ # Skip lockfile-based external dep version tracking
179
+ bw ls-affected --ignore-external-deps
180
+
181
+ # Bypass git entirely with an explicit list of changed files
182
+ # (paths, dirs, globs; '!' to exclude; whitespace-separated)
183
+ bw ls-affected --files="packages/example/**/*.ts packages/example/my-file.json"
184
+ bw ls-affected -F "packages/a/**/*.ts !packages/a/**/*.test.ts"
185
+
186
+ # Per-workspace summary of why each workspace is affected
187
+ bw ls-affected --explain
188
+ bw ls-affected -e
189
+
190
+ # Full per-file changes and dep cascade chain for each affected workspace
191
+ bw ls-affected --explain --detailed
192
+ bw ls-affected -e -D
193
+
194
+ # JSON output (with --explain produces the full result object)
195
+ bw ls-affected --json --pretty
196
+ bw ls-affected --explain --json --pretty
197
+
198
+ # Run a script across affected workspaces (accepts the same affected options
199
+ # as ls-affected, plus the same script-execution options as run-script:
200
+ # --parallel, --dep-order, --args, --output-style, --inline, etc.)
201
+ bw run-affected build
202
+ bw run-affected build --base=my-branch --ignore-uncommitted --dep-order
203
+ bw run-affected build --files="packages/a/src/**/*.ts" --parallel=2
204
+ bw run-affected "bun build" --inline --inline-name=build # inline command form
205
+
206
+ ### Global Options ###
207
+ # Root directory of project:
208
+ bw --cwd=/path/to/project ls
209
+ bw -d /path/to/project ls
210
+
211
+ # Include root workspace as a normal workspace (default false):
212
+ bw --include-root ls
213
+ bw -r ls
214
+ bw --no-include-root ls # override config/env var setting
215
+
216
+ # Log level (debug|info|warn|error|silent, default info)
217
+ bw --log-level=silent ls
218
+ bw -l silent ls
219
+ ```
220
+
221
+ ### API examples:
222
+
223
+ The API is held in close parity with the CLI. It is developed first so that the CLI is a thin wrapper around the API.
224
+
225
+ ```typescript
226
+ import { createFileSystemProject } from "bun-workspaces";
227
+
228
+ const project = createFileSystemProject({
229
+ // the options object itself and its properties are optional
230
+ rootDirectory: "path/to/your/project",
231
+ includeRootWorkspace: false,
232
+ });
233
+ project.workspaces; // array of all workspaces in the project
234
+ project.rootWorkspace; // the root workspace (available even when not included in the workspaces array)
235
+ project.findWorkspaceByName("my-workspace"); // find a workspace by name
236
+ project.findWorkspaceByAlias("my-alias"); // find a workspace by alias
237
+ project.findWorkspaceByNameOrAlias("my-workspace-or-alias"); // find a workspace by name or alias
238
+ project.findWorkspacesByPattern(
239
+ "my-workspace-name",
240
+ "my-workspace-alias",
241
+ "my-name-pattern-*",
242
+ "alias:my-alias-*",
243
+ "path:my-glob/**/*",
244
+ ); // find workspaces by pattern like the CLI
245
+ project.runWorkspaceScript({
246
+ workspaceNameOrAlias: "my-workspace",
247
+ script: "lint",
248
+ inline: true,
249
+ // args can be a string or an array of strings
250
+ // if string, the argv will be parsed POSIX-style
251
+ args: "--my-arg=value",
252
+ });
253
+ project.runScriptAcrossWorkspaces({
254
+ script: "lint",
255
+ workspacePatterns: [
256
+ "alias:my-alias-pattern-*",
257
+ "path:my-glob/**/*",
258
+ "workspace-name-a",
259
+ "workspace-alias-b",
260
+ ],
261
+ parallel: true, // also could be { max: 2 }, max taking same options as seen in CLI examples above (e.g. "50%", "auto", etc.)
262
+ dependencyOrder: true,
263
+ ignoreDependencyFailure: true,
264
+ // same as for runWorkspaceScript
265
+ args: ["--my", "--appended", "--args"],
266
+ // Optional, callback when script starts, skips, or exits
267
+ onScriptEvent: (event, { workspace, exitResult }) => {
268
+ // event: "start", "skip", "exit"
269
+ },
270
+ });
271
+
272
+ // Determine affected workspaces — git mode (default)
273
+ project.determineAffectedWorkspaces({
274
+ diffSource: "git",
275
+ // optional: resolve inputs for a specific script (uses scripts[name].inputs)
276
+ script: "build",
277
+ // optional: skip workspace dep cascade
278
+ ignoreWorkspaceDependencies: false,
279
+ // optional: skip lockfile-based external dep version tracking
280
+ ignoreExternalDependencies: false,
281
+ diffOptions: {
282
+ baseRef: "main", // default from config / "main"
283
+ headRef: "HEAD", // default
284
+ ignoreUncommitted: false, // staged + unstaged + untracked
285
+ ignoreUntracked: false,
286
+ ignoreUnstaged: false,
287
+ ignoreStaged: false,
288
+ },
289
+ });
290
+
291
+ // Determine affected workspaces — fileList mode (bypass git)
292
+ project.determineAffectedWorkspaces({
293
+ diffSource: "fileList",
294
+ // paths, directories, or globs (relative to project root); '!' to exclude
295
+ changedFiles: ["packages/a/**/*.ts", "!packages/a/**/*.test.ts"],
296
+ });
297
+
298
+ // Run a script across affected workspaces. Accepts the same affected options
299
+ // as determineAffectedWorkspaces, plus the script-execution options from
300
+ // runScriptAcrossWorkspaces (parallel, dependencyOrder, args, onScriptEvent, etc.).
301
+ project.runAffectedWorkspaceScript({
302
+ script: "build",
303
+ diffSource: "git",
304
+ diffOptions: { baseRef: "main", ignoreUncommitted: true },
305
+ parallel: { max: 2 },
306
+ dependencyOrder: true,
307
+ ignoreDependencyFailure: true,
308
+ });
309
+ ```
310
+
311
+ ## The Workspace object
312
+
313
+ ```jsonc
314
+ {
315
+ // The name of the workspace from its package.json
316
+ "name": "my-workspace",
317
+ // Whether the workspace is the root workspace
318
+ "isRoot": false,
319
+ // The relative path to the workspace from the project root
320
+ "path": "my/workspace/path",
321
+ // The glob pattern from the root package.json "workspaces" field
322
+ // that this workspace was matched from
323
+ "matchPattern": "my/workspace/pattern/*",
324
+ // The scripts available in the workspace's package.json
325
+ "scripts": ["my-script"],
326
+ // Aliases defined in workspace configuration (bw.workspace.jsonc/bw.workspace.json)
327
+ "aliases": ["my-alias"],
328
+ // Tags defined in workspace configuration
329
+ "tags": ["my-tag"],
330
+ // Names of other workspaces that this workspace depends on
331
+ "dependencies": ["my-dependency"],
332
+ // Names of other workspaces that depend on this workspace
333
+ "dependents": ["my-dependent"],
334
+ // Non-workspace package deps declared in package.json (across all four maps).
335
+ // `source` is one of "dependencies" | "devDependencies" | "peerDependencies" | "optionalDependencies".
336
+ // `version` is the package.json range, with `catalog:`/`catalog:<name>` resolved when possible.
337
+ // `catalog` is present when declared via a catalog ref.
338
+ "externalDependencies": [
339
+ { "name": "lodash", "version": "^4.17.0", "source": "dependencies" },
340
+ { "name": "typescript", "version": "^5.0.0", "source": "devDependencies" },
341
+ {
342
+ "name": "react",
343
+ "version": "^18.0.0",
344
+ "source": "dependencies",
345
+ "catalog": { "name": "" },
346
+ },
347
+ ],
348
+ }
349
+ ```
350
+
351
+ ## Root config
352
+
353
+ Optional project config can be placed in `bw.root.ts`/`bw.root.js`/`bw.root.jsonc`/`bw.root.json` in the root directory, or in the `"bw"` key of `package.json`.
354
+
355
+ Config defaults here take precedence over environment variables. Explicit CLI arguments or API options take precedence over all other settings.
356
+
357
+ ```jsonc
358
+ {
359
+ "defaults": {
360
+ "parallelMax": 5, // same options as seen in CLI examples above
361
+ "shell": "system", // "bun" or "system" (default "bun")
362
+ "includeRootWorkspace": true, // treat root package.json as a normal workspace
363
+ "affectedBaseRef": "main", // default git base ref for affected resolution (env: BW_AFFECTED_BASE_REF_DEFAULT)
364
+ },
365
+ "workspacePatternConfigs": [
366
+ // see Workspace Pattern Configs section below
367
+ ],
368
+ }
369
+ ```
370
+
371
+ ### mergeRootConfig
372
+
373
+ `mergeRootConfig` merges multiple root configs left to right. Later configs take precedence for scalar fields. `workspacePatternConfigs` entries are concatenated. Any argument may be a factory function `(prev: RootConfig) => RootConfig`.
374
+
375
+ ```ts
376
+ import { mergeRootConfig } from "bun-workspaces/config";
377
+
378
+ export default mergeRootConfig(
379
+ { defaults: { parallelMax: 4 } },
380
+ { defaults: { shell: "system" } },
381
+ (prevConfig) => ({ defaults: { includeRootWorkspace: true } }),
382
+ );
383
+ ```
384
+
385
+ ## Workspace config
386
+
387
+ Optional config can be placed in `bw.workspace.ts`/`bw.workspace.js`/`bw.workspace.jsonc`/`bw.workspace.json` in a workspace directory, or in the `"bw"` key of `package.json`.
388
+
389
+ Aliases must be unique to each workspace and must not clash with other workspaces' `package.json` names.
390
+
391
+ Tags are strings to group workspaces together; they do not need to be unique.
392
+
393
+ ```jsonc
394
+ {
395
+ "alias": "my-alias", // can be array
396
+ "tags": ["my-tag"],
397
+ // Default inputs used to determine if the workspace is affected, applied to
398
+ // all scripts that don't configure their own inputs. See "Inputs" below.
399
+ "defaultInputs": {
400
+ "files": ["src/**/*.ts", "!src/**/*.test.ts"],
401
+ "workspacePatterns": ["tag:shared-lib"],
402
+ "externalDependencies": ["lodash", "react"],
403
+ },
404
+ "scripts": {
405
+ "lint": {
406
+ // set optional sorting order for scripts
407
+ "order": 1,
408
+ },
409
+ "build": {
410
+ // per-script inputs override defaultInputs for this script's affected resolution
411
+ "inputs": {
412
+ "files": ["src/**/*.ts", "/shared-types/**/*.ts"], // leading "/" = relative to the project root
413
+ },
414
+ },
415
+ },
416
+ "rules": {
417
+ "workspaceDependencies": {
418
+ // allowPatterns: only workspaces matching these patterns are permitted as dependencies
419
+ "allowPatterns": ["my-allow-pattern-*"],
420
+ // denyPatterns: workspaces matching these patterns are forbidden as dependencies.
421
+ // When combined with allowPatterns, deny filters within the allowed subset.
422
+ "denyPatterns": ["my-deny-pattern-*"],
423
+ },
424
+ },
425
+ }
426
+ ```
427
+
428
+ ### Inputs
429
+
430
+ The `defaultInputs` field (and the per-script `scripts[name].inputs` field) controls what counts as an input for [affected workspace](#affected-workspaces) resolution. Both have the same shape (`WorkspaceInputsConfig`):
431
+
432
+ - `files` — file paths, directories, or globs relative to the workspace's directory. Leading `/` makes a pattern relative to the project root. Prefix with `!` to exclude. Only git-trackable files are matched. Default when not provided is `["."]` (everything in the workspace dir).
433
+ - `workspacePatterns` — workspace patterns whose matched workspaces are treated as inputs (like dependencies, but without needing a real `package.json` dep edge).
434
+ - `externalDependencies` — allowlist of package names that participate in lockfile-change detection. Omitted = all external deps participate; `[]` = none participate; non-empty list = only listed names participate (intersected with the workspace's actual external deps from `package.json`).
435
+
436
+ Per-script `inputs` fully replaces `defaultInputs` for that script — the two are not merged. If a script has its own `inputs` field, `defaultInputs` is ignored for that script.
437
+
438
+ ### Workspace Dependency Rules
439
+
440
+ Using the `rules.workspaceDependencies` field, you can define rules for which workspaces are allowed to be dependencies, using `allowPatterns`, `denyPatterns`, or both.
441
+
442
+ `allowPatterns` defines the permitted subset of dependencies. `denyPatterns` forbids specific dependencies. When both are present, `denyPatterns` further filters within the subset permitted by `allowPatterns`.
443
+
444
+ Workspace Patterns are used to match workspaces.
445
+
446
+ ### mergeWorkspaceConfig
447
+
448
+ `mergeWorkspaceConfig` merges multiple workspace configs left to right. Arrays (`alias`, `tags`, `allowPatterns`, `denyPatterns`) are concatenated and deduplicated. Scalar fields later wins. `scripts` are deep-merged per key. Any argument may be a factory function `(prev: WorkspaceConfig) => WorkspaceConfig`.
449
+
450
+ ```ts
451
+ import { mergeWorkspaceConfig } from "bun-workspaces/config";
452
+
453
+ export default mergeWorkspaceConfig(
454
+ { alias: "a", tags: ["x"] },
455
+ { alias: "b", scripts: { build: { order: 1 } } },
456
+ (prevConfig) => ({ tags: ["y"] }),
457
+ );
458
+ // result: { alias: ["a", "b"], tags: ["x", "y"], scripts: { build: { order: 1 } } }
459
+ ```
460
+
461
+ ## Workspace Pattern Configs
462
+
463
+ The root config's `workspacePatternConfigs` field applies workspace configs to groups of workspaces matched by [workspace patterns](/concepts/workspace-patterns). Entries are applied in order, left to right.
464
+
465
+ Each entry's `config` is merged into the accumulated config of all matching workspaces using the same semantics as `mergeWorkspaceConfig`. The local workspace config (from `bw.workspace.*` or `package.json`) is always the starting base.
466
+
467
+ Pattern matching reflects the accumulated state: aliases and tags added by earlier entries are visible to later entries' patterns.
468
+
469
+ ```ts
470
+ import { defineRootConfig } from "bun-workspaces/config";
471
+
472
+ export default defineRootConfig({
473
+ workspacePatternConfigs: [
474
+ {
475
+ patterns: ["path:packages/apps/**/*"],
476
+ config: { tags: ["app"] },
477
+ },
478
+ {
479
+ // "tag:app" matches because the entry above added it
480
+ patterns: ["tag:app"],
481
+ config: {
482
+ rules: { workspaceDependencies: { allowPatterns: ["tag:lib"] } },
483
+ },
484
+ },
485
+ {
486
+ patterns: ["tag:app"],
487
+ // Factory form: JS/TS only — receives static workspace data and accumulated config
488
+ config: (workspace, prevConfig) => ({
489
+ alias: workspace.name.replace(/^@my-scope\//, ""),
490
+ }),
491
+ },
492
+ ],
493
+ });
494
+ ```
495
+
496
+ ### Factory function context (`RawWorkspace`)
497
+
498
+ The factory `(workspace: RawWorkspace, prevConfig: ResolvedWorkspaceConfig) => WorkspaceConfig` receives:
499
+
500
+ - `workspace.name` — package name from package.json
501
+ - `workspace.isRoot` — whether this is the root workspace
502
+ - `workspace.path` — relative path from project root
503
+ - `workspace.matchPattern` — glob from root package.json `workspaces` field that matched
504
+ - `workspace.scripts` — sorted list of script names from package.json
505
+ - `workspace.dependencies` — names of workspace dependencies
506
+ - `workspace.dependents` — names of workspaces that depend on this one
507
+
508
+ `prevConfig` is the fully resolved workspace config at that point, including the local config and any configs applied by earlier pattern entries. It has `aliases: string[]`, `tags: string[]`, `scripts: Record<string, ScriptConfig>`, `rules: WorkspaceRules`, `defaultInputs?: WorkspaceInputsConfig`.
509
+
510
+ ## TypeScript/JSON Config Files
511
+
512
+ ### TypeScript
513
+
514
+ `bw.workspace.ts`
515
+
516
+ ```ts
517
+ import { defineWorkspaceConfig } from "bun-workspaces/config";
518
+
519
+ export default defineWorkspaceConfig({
520
+ alias: "my-alias",
521
+ tags: ["my-tag"],
522
+ });
523
+ ```
524
+
525
+ `bw.root.ts`
526
+
527
+ ```ts
528
+ import { defineRootConfig } from "bun-workspaces/config";
529
+
530
+ export default defineRootConfig({
531
+ defaults: {
532
+ parallelMax: 5,
533
+ },
534
+ });
535
+ ```
536
+
537
+ <!-- bun-workspaces (npm package) agent documentation end -->
package/README.md CHANGED
@@ -2,25 +2,29 @@
2
2
  <img src="./workspaces/web/documentation-website/src/pages/public/images/png/bwunster-bg-banner-wide_3000x900.png" alt="bun-workspaces" width="100%" />
3
3
  </a>
4
4
 
5
- # bun-workspaces
5
+ <br/>
6
6
 
7
- ### [**See Full Documentation Here**: _https://bunworkspaces.com_](https://bunworkspaces.com)
7
+ Full Documentation: [https://bunworkspaces.com](https://bunworkspaces.com)
8
8
 
9
- This is a CLI and TypeScript API to enhance your monorepo development with Bun's [native workspaces](https://bun.sh/docs/install/workspaces) feature for nested JavaScript/TypeScript packages.
9
+ Changelog: [GitHub Releases](https://github.com/bun-workspaces/bun-workspaces/releases)
10
10
 
11
- - Works right away, with no boilerplate required 🍔🍴
12
- - Get metadata about your monorepo 🤖
13
- - Orchestrate your workspaces' `package.json` scripts 📋
14
- - Run inline [Bun Shell](https://bun.com/docs/runtime/shell) scripts in workspaces 🐚
15
- - Use the [MCP server](https://bunworkspaces.com/ai/mcp) for your AI tooling to learn how to use `bun-workspaces` and add project metadata to context! 🛠️
11
+ # bun-workspaces
16
12
 
17
- This is a tool to help manage a Bun monorepo, offering features beyond what [Bun's --filter feature](https://bun.com/docs/pm/filter) can do. It can be used to get a variety of metadata about your project and run scripts across your workspaces with advanced control.
13
+ A [monorepo](http://sonarsource.com/resources/library/monorepo/) tool that enhances native [Bun workspaces](https://bun.sh/docs/install/workspaces).
18
14
 
19
- To get started, all you need is a repo using [Bun's workspaces feature](https://bun.sh/docs/install/workspaces) for nested JavaScript/TypeScript packages.
15
+ - Works right away, with **no boilerplate required** 🍽️
16
+ - Get **rich metadata** about your monorepo 🤖
17
+ - **Orchestrate** your workspaces' package.json scripts 🎻
18
+ - Run one-off [**Bun Shell**](https://bun.com/docs/runtime/shell) commands in your workspaces 🐚
19
+ - Use with Bun as your package manager for **Node** projects 🎁
20
+ - Determine **affected workspaces** based on changed files 🕸️
21
+ - AI: Provides an [AGENTS.md](https://bunworkspaces.com/ai/agents) file and an [MCP server](https://bunworkspaces.com/ai/mcp)! 🛠️
20
22
 
21
- This package is unopinionated and works with any project structure you want. Think of this as a power suit you can snap onto native workspaces, rather than whole new monorepo framework.
23
+ To get started, all you need is a repo using Bun's workspaces feature for nested JavaScript/TypeScript packages. This adds enhanced features on top of plain workspaces.
22
24
 
23
- Start running some [CLI commands](https://bunworkspaces.com/cli) right away in your repo, or take full advantage of the [scripting API](https://bunworkspaces.com/api) and its features.
25
+ Start running some [CLI commands](https://bunworkspaces.com/cli) right away in your repo, or take full advantage of the [TypeScript API](https://bunworkspaces.com/api) and its features.
26
+
27
+ This package is unopinionated and works with any project structure you want. Think of this as a power suit you can snap onto native workspaces, rather than whole new monorepo framework.
24
28
 
25
29
  ## Quick Start
26
30
 
@@ -101,6 +105,18 @@ bw run my-script --output-style=prefixed
101
105
  # Use the plain output style (no workspace prefixes)
102
106
  bw run my-script --output-style=plain
103
107
 
108
+ # List affected workspaces based on git diff (main vs. HEAD when not configured)
109
+ bw list-affected
110
+
111
+ # Set the git base and head for comparison
112
+ bw list-affected --base=my-branch-a --head=my-branch-b
113
+
114
+ # See detailed reasons for affected workspaces
115
+ bw list-affected --explain --detailed
116
+
117
+ # Run a script across the workspaces affected by a change
118
+ bw run-affected my-script
119
+
104
120
  # Silence all output of the run command
105
121
  bw --log-level=silent run my-script --output-style=none
106
122
 
@@ -262,9 +278,26 @@ import { defineWorkspaceConfig } from "bun-workspaces/config";
262
278
  export default defineWorkspaceConfig({
263
279
  alias: "my-web-app", // shorthand name; use array for multiple
264
280
  tags: ["app", "frontend"],
281
+ // Optional, for configuring affected workspace resolution inputs
282
+ // Applies to all scripts that don't configure their own inputs
283
+ defaultInputs: {
284
+ // File paths, directory paths, or globs relative to the workspace's path.
285
+ // Default is all git-trackable files in the workspace directory.
286
+ files: ["src/**/*.ts", "!src/**/*.test.ts"],
287
+ // Workspaces to treat like dependencies that aren't package.json dependencies
288
+ workspacePatterns: ["tag:lib"],
289
+ // Dependency names (e.g. "react") to treat as dependencies (default: all)
290
+ externalDependencies: ["react"],
291
+ },
265
292
  scripts: {
266
293
  // lower order runs first in sequenced script execution
267
- build: { order: 1 },
294
+ build: {
295
+ // Optional, for setting the default script execution order
296
+ order: 1,
297
+ // Optional, for configuring affected workspace resolution inputs
298
+ // Applies to the build script only
299
+ inputs: { files: ["src/**/*.ts"] },
300
+ },
268
301
  test: { order: 2 },
269
302
  },
270
303
  rules: {
@@ -320,6 +353,11 @@ export default defineRootConfig({
320
353
  // "tag:app" matches because the first entry added it
321
354
  patterns: ["tag:app"],
322
355
  config: {
356
+ // Inputs always override previous entries instead of deep merging
357
+ defaultInputs: { files: ["src/**/*.ts"] },
358
+ scripts: {
359
+ build: { order: 1, inputs: { files: ["src/**/*.ts"] } },
360
+ },
323
361
  rules: {
324
362
  workspaceDependencies: {
325
363
  allowPatterns: ["tag:lib"], // apps may only depend on libs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-workspaces",
3
- "version": "1.8.2",
3
+ "version": "1.10.0",
4
4
  "description": "A monorepo management tool for Bun, with a CLI and API to enhance Bun's native workspaces.",
5
5
  "license": "MIT",
6
6
  "exports": {