bun-workspaces 1.8.2 β†’ 1.9.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 (43) hide show
  1. package/README.md +51 -13
  2. package/package.json +1 -1
  3. package/src/2392.mjs +184 -3
  4. package/src/5166.mjs +1 -0
  5. package/src/8529.mjs +10 -0
  6. package/src/affected/affectedBaseRef.mjs +12 -0
  7. package/src/affected/externalDependencyChanges.mjs +47 -0
  8. package/src/affected/fileAffectedWorkspaces.mjs +145 -53
  9. package/src/affected/gitAffectedFiles.mjs +44 -1
  10. package/src/affected/gitAffectedWorkspaces.mjs +73 -3
  11. package/src/affected/index.mjs +2 -0
  12. package/src/ai/mcp/serverState.mjs +1 -1
  13. package/src/cli/commands/commandHandlerUtils.mjs +12 -7
  14. package/src/cli/commands/commands.mjs +4 -1
  15. package/src/cli/commands/handleSimpleCommands.mjs +2 -2
  16. package/src/cli/commands/listAffected.mjs +184 -0
  17. package/src/cli/commands/runScript/handleRunAffected.mjs +99 -0
  18. package/src/cli/commands/runScript/handleRunScript.mjs +19 -202
  19. package/src/cli/commands/runScript/index.mjs +1 -0
  20. package/src/cli/commands/runScript/scriptRunFlow.mjs +213 -0
  21. package/src/cli/index.d.ts +749 -134
  22. package/src/config/public.d.ts +66 -2
  23. package/src/config/rootConfig/rootConfig.mjs +4 -0
  24. package/src/config/rootConfig/rootConfigSchema.mjs +3 -0
  25. package/src/config/workspaceConfig/mergeWorkspaceConfig.mjs +33 -19
  26. package/src/config/workspaceConfig/workspaceConfig.mjs +3 -0
  27. package/src/config/workspaceConfig/workspaceConfigSchema.mjs +26 -0
  28. package/src/index.d.ts +307 -5
  29. package/src/index.mjs +1 -0
  30. package/src/internal/bun/bunLock.mjs +33 -0
  31. package/src/internal/generated/aiDocs/docs.mjs +152 -3
  32. package/src/internal/generated/ajv/validateRootConfig.mjs +1 -1
  33. package/src/internal/generated/ajv/validateWorkspaceConfig.mjs +1 -1
  34. package/src/project/implementations/fileSystemProject/affectedWorkspaces.mjs +225 -0
  35. package/src/project/implementations/{fileSystemProject.mjs β†’ fileSystemProject/fileSystemProject.mjs} +169 -12
  36. package/src/project/implementations/fileSystemProject/index.mjs +4 -0
  37. package/src/project/implementations/memoryProject.mjs +1 -0
  38. package/src/project/index.mjs +1 -1
  39. package/src/rslib-runtime.mjs +0 -31
  40. package/src/workspaces/applyWorkspacePatternConfigs.mjs +10 -1
  41. package/src/workspaces/dependencyGraph/resolveDependencies.mjs +68 -18
  42. package/src/workspaces/findWorkspaces.mjs +1 -0
  43. package/src/workspaces/workspace.mjs +8 -2
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
+ - Use the [MCP server](https://bunworkspaces.com/ai/mcp) to make your AI tooling aware of `bun-workspaces` and its documentation resources! πŸ› οΈ
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.9.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": {
package/src/2392.mjs CHANGED
@@ -28,7 +28,8 @@ const CLI_COMMANDS_CONFIG = {
28
28
  options: {
29
29
  workspacePatterns: {
30
30
  flags: ["-W", "--workspace-patterns <patterns>"],
31
- description: "Workspace patterns to match, separated by spaces",
31
+ description:
32
+ "Workspace patterns to match, separated by whitespace. Use backslashes to escape spaces if needed.",
32
33
  },
33
34
  nameOnly: {
34
35
  flags: ["-n", "--name-only"],
@@ -144,6 +145,79 @@ const CLI_COMMANDS_CONFIG = {
144
145
  "Start the bun-workspaces MCP (Model Context Protocol) server over stdio",
145
146
  options: {},
146
147
  },
148
+ listAffected: {
149
+ command: "list-affected",
150
+ isGlobal: false,
151
+ aliases: ["ls-affected"],
152
+ description:
153
+ "List workspaces affected by a set of changed files (git or file list)",
154
+ options: {
155
+ base: {
156
+ flags: ["-B", "--base <ref>"],
157
+ description:
158
+ "Git base ref to diff against (default is main if not configured). Cannot be used with --files",
159
+ },
160
+ head: {
161
+ flags: ["-H", "--head <ref>"],
162
+ description:
163
+ "Git head ref to diff against (default: HEAD). Cannot be used with --files",
164
+ },
165
+ files: {
166
+ flags: ["-F", "--files <files>"],
167
+ description:
168
+ "Changed files (paths/dirs/globs, '!' to exclude), separated by spaces. Use backslashes to escape spaces if needed. Bypasses git, so cannot be used with --base or --head.",
169
+ },
170
+ script: {
171
+ flags: ["-S", "--script <script>"],
172
+ description: "Resolve inputs for the named script",
173
+ },
174
+ ignoreUntracked: {
175
+ flags: ["--ignore-untracked"],
176
+ description: "Exclude untracked files",
177
+ },
178
+ ignoreUnstaged: {
179
+ flags: ["--ignore-unstaged"],
180
+ description: "Exclude unstaged files",
181
+ },
182
+ ignoreStaged: {
183
+ flags: ["--ignore-staged"],
184
+ description: "Exclude staged files",
185
+ },
186
+ ignoreUncommitted: {
187
+ flags: ["--ignore-uncommitted"],
188
+ description:
189
+ "Exclude all uncommitted changes (staged, unstaged, untracked)",
190
+ },
191
+ ignoreWorkspaceDeps: {
192
+ flags: ["--ignore-workspace-deps"],
193
+ description:
194
+ "Ignore workspace dependencies derived from package.json files",
195
+ },
196
+ ignoreExternalDeps: {
197
+ flags: ["--ignore-external-deps"],
198
+ description:
199
+ "Ignore changes to external dependencies (e.g. npm packages) versions in bun.lock",
200
+ },
201
+ explain: {
202
+ flags: ["-e", "--explain"],
203
+ description:
204
+ "Include changed-file counts and dependency reasons. With --json, outputs the full result object",
205
+ },
206
+ detailed: {
207
+ flags: ["-D", "--detailed"],
208
+ description:
209
+ "With --explain, render full per-file data and dependency edge chains",
210
+ },
211
+ json: {
212
+ flags: JSON_FLAGS,
213
+ description: "Output as JSON",
214
+ },
215
+ pretty: {
216
+ flags: PRETTY_FLAGS,
217
+ description: "Pretty print JSON",
218
+ },
219
+ },
220
+ },
147
221
  runScript: {
148
222
  command: "run-script [script] [workspacePatterns...]",
149
223
  isGlobal: false,
@@ -153,11 +227,118 @@ const CLI_COMMANDS_CONFIG = {
153
227
  options: {
154
228
  script: {
155
229
  flags: ["-S", "--script <script>"],
156
- description: "The script to run.",
230
+ description: "The script to run. (Alternative to positional argument)",
157
231
  },
158
232
  workspacePatterns: {
159
233
  flags: ["-W", "--workspace-patterns <patterns>"],
160
- description: "Workspace patterns to match, separated by spaces.",
234
+ description:
235
+ "Workspace patterns to match, separated by spaces. (Alternative to positional arguments)",
236
+ },
237
+ parallel: {
238
+ flags: ["-P", "--parallel [max]"],
239
+ description:
240
+ 'Run the scripts in parallel. Pass "false" for series, or a concurrency limit as a number, percentage ("50%"), "auto", "default", or"unbounded"',
241
+ },
242
+ args: {
243
+ flags: ["-a", "--args <args>"],
244
+ description: "Args to append to the script command",
245
+ },
246
+ outputStyle: {
247
+ flags: ["-o", "--output-style <style>"],
248
+ description: "The output style to use",
249
+ values: [...OUTPUT_STYLE_VALUES],
250
+ },
251
+ groupedLines: {
252
+ flags: ["-L", "--grouped-lines <count>"],
253
+ description: `With grouped output, the max preview lines (number or "auto", default "auto")`,
254
+ },
255
+ noPrefix: {
256
+ flags: ["-N", "--no-prefix"],
257
+ description: "(DEPRECATED) Use --output-style=plain instead",
258
+ deprecated: true,
259
+ },
260
+ inline: {
261
+ flags: ["-i", "--inline"],
262
+ description:
263
+ "Run the script as an inline command from the workspace directory",
264
+ },
265
+ inlineName: {
266
+ flags: ["-I", "--inline-name <name>"],
267
+ description: "An optional name for the script when --inline is passed",
268
+ },
269
+ shell: {
270
+ flags: ["-s", "--shell <shell>"],
271
+ values: [...SCRIPT_SHELL_OPTIONS, "default"],
272
+ description: `When using --inline, the shell to use to run the script`,
273
+ },
274
+ depOrder: {
275
+ flags: ["-d", "--dep-order"],
276
+ description:
277
+ "Scripts for dependent workspaces run only after their dependencies",
278
+ },
279
+ ignoreDepFailure: {
280
+ flags: ["-f", "--ignore-dep-failure"],
281
+ description:
282
+ "In dependency order, continue running scripts even if a dependency fails",
283
+ },
284
+ jsonOutfile: {
285
+ flags: ["-j", "--json-outfile <file>"],
286
+ description: "Output results in a JSON file",
287
+ },
288
+ },
289
+ },
290
+ runAffected: {
291
+ command: "run-affected [script]",
292
+ isGlobal: false,
293
+ aliases: [],
294
+ description:
295
+ "Run a script across the workspaces affected by a set of changed files (git or file list)",
296
+ options: {
297
+ script: {
298
+ flags: ["-S", "--script <script>"],
299
+ description: "The script to run. (Alternative to positional argument)",
300
+ },
301
+ base: {
302
+ flags: ["-B", "--base <ref>"],
303
+ description:
304
+ "Git base ref to diff against (default is main if not configured). Cannot be used with --files",
305
+ },
306
+ head: {
307
+ flags: ["-H", "--head <ref>"],
308
+ description:
309
+ 'Git head ref to diff against (default "HEAD"). Cannot be used with --files',
310
+ },
311
+ files: {
312
+ flags: ["-F", "--files <files>"],
313
+ description:
314
+ "Changed files (paths/dirs/globs, '!' to exclude), separated by whitespace. Use backslashes to escape spaces if needed. Bypasses git, so cannot be used with --base or --head.",
315
+ },
316
+ ignoreUntracked: {
317
+ flags: ["--ignore-untracked"],
318
+ description: "Exclude untracked files",
319
+ },
320
+ ignoreUnstaged: {
321
+ flags: ["--ignore-unstaged"],
322
+ description: "Exclude unstaged files",
323
+ },
324
+ ignoreStaged: {
325
+ flags: ["--ignore-staged"],
326
+ description: "Exclude staged files",
327
+ },
328
+ ignoreUncommitted: {
329
+ flags: ["--ignore-uncommitted"],
330
+ description:
331
+ "Exclude all uncommitted changes (staged, unstaged, untracked)",
332
+ },
333
+ ignoreWorkspaceDeps: {
334
+ flags: ["--ignore-workspace-deps"],
335
+ description:
336
+ "Ignore workspace dependencies derived from package.json files",
337
+ },
338
+ ignoreExternalDeps: {
339
+ flags: ["--ignore-external-deps"],
340
+ description:
341
+ "Ignore changes to external dependencies (e.g. npm packages) versions in bun.lock",
161
342
  },
162
343
  parallel: {
163
344
  flags: ["-P", "--parallel [max]"],
package/src/5166.mjs CHANGED
@@ -2,6 +2,7 @@ const USER_ENV_VARS = {
2
2
  parallelMaxDefault: "BW_PARALLEL_MAX_DEFAULT",
3
3
  scriptShellDefault: "BW_SHELL_DEFAULT",
4
4
  includeRootWorkspaceDefault: "BW_INCLUDE_ROOT_WORKSPACE_DEFAULT",
5
+ affectedBaseRefDefault: "BW_AFFECTED_BASE_REF_DEFAULT",
5
6
  };
6
7
  const getUserEnvVarName = (key) => USER_ENV_VARS[key];
7
8
 
package/src/8529.mjs CHANGED
@@ -16,8 +16,18 @@ const CONFIG_LOCATION_PATHS = {
16
16
  jsonFile: (name) => `${name}.json`,
17
17
  packageJson: (_, packageJsonKey) => `package.json["${packageJsonKey}"]`,
18
18
  };
19
+ const CONFIG_LOCATION_DESCRIPTIONS =
20
+ /* unused pure expression or super */ null && {
21
+ tsFile: "TypeScript file",
22
+ jsFile: "JavaScript file",
23
+ jsoncFile: "JSONC file",
24
+ jsonFile: "JSON file",
25
+ packageJson: "package.json key",
26
+ };
19
27
  const createConfigLocationPath = (locationType, name, packageJsonKey) =>
20
28
  CONFIG_LOCATION_PATHS[locationType](name, packageJsonKey);
29
+ const createConfigLocationDescription = (locationType) =>
30
+ CONFIG_LOCATION_DESCRIPTIONS[locationType];
21
31
 
22
32
  export {
23
33
  CONFIG_LOCATION_TYPES,
@@ -0,0 +1,12 @@
1
+ import { getUserEnvVar } from "../config/userEnvVars/index.mjs";
2
+
3
+ const DEFAULT_AFFECTED_BASE_REF = "main";
4
+ /**
5
+ * Resolves the default base ref for affected workspace resolution.
6
+ *
7
+ * Precedence: explicit value (typically from root config defaults) >
8
+ * `BW_AFFECTED_BASE_REF_DEFAULT` env var > `"main"`.
9
+ */ const resolveDefaultAffectedBaseRef = (value) =>
10
+ value || getUserEnvVar("affectedBaseRefDefault") || DEFAULT_AFFECTED_BASE_REF;
11
+
12
+ export { DEFAULT_AFFECTED_BASE_REF, resolveDefaultAffectedBaseRef };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Resolve a dep's version for a specific workspace. bun.lock encodes
3
+ * divergent per-workspace resolutions under a `<workspaceName>/<depName>` key
4
+ * when the workspace's range can't dedupe with the hoisted version. Always
5
+ * consult that namespaced key first; fall back to the bare key for the
6
+ * common (hoisted) case.
7
+ */ const resolveWorkspaceDepVersion = ({ lock, workspaceName, depName }) =>
8
+ lock.get(`${workspaceName}/${depName}`) ?? lock.get(depName) ?? null;
9
+ /**
10
+ * Given each workspace's declared external deps and lockfile version maps
11
+ * at base vs head, emit per-workspace change entries for any external dep
12
+ * whose resolved version differs (including added/removed).
13
+ *
14
+ * Pure function. No I/O.
15
+ */ const computeExternalDependencyChanges = ({
16
+ workspaces,
17
+ baseLock,
18
+ headLock,
19
+ }) => {
20
+ const result = new Map();
21
+ for (const workspace of workspaces) {
22
+ const changes = [];
23
+ for (const { name, source } of workspace.externalDependencies) {
24
+ const baseVersion = resolveWorkspaceDepVersion({
25
+ lock: baseLock,
26
+ workspaceName: workspace.name,
27
+ depName: name,
28
+ });
29
+ const headVersion = resolveWorkspaceDepVersion({
30
+ lock: headLock,
31
+ workspaceName: workspace.name,
32
+ depName: name,
33
+ });
34
+ if (baseVersion === headVersion) continue;
35
+ changes.push({
36
+ name,
37
+ source,
38
+ baseVersion,
39
+ headVersion,
40
+ });
41
+ }
42
+ if (changes.length) result.set(workspace.name, changes);
43
+ }
44
+ return result;
45
+ };
46
+
47
+ export { computeExternalDependencyChanges };