bun-workspaces 1.7.0 → 1.8.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.
package/README.md CHANGED
@@ -6,20 +6,13 @@
6
6
 
7
7
  ### [**See Full Documentation Here**: _https://bunworkspaces.com_](https://bunworkspaces.com)
8
8
 
9
- **Big Recent Updates!**
10
-
11
- - Version 1 is here after the initial alpha! 🍔🍔👁️🍔🍔
12
- - You can demo the CLI [directly in the browser](https://bunworkspaces.com/web-cli)
13
- - There's now [an official blog](https://bunworkspaces.com/blog/bun-workspaces-v1) to cover noteworthy releases and more!
14
- - There's now a provided [MCP server](https://bunworkspaces.com/ai/mcp) for your AI tooling to understand how to use `bun-workspaces`!
15
- <hr/>
16
-
17
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.
18
10
 
19
11
  - Works right away, with no boilerplate required 🍔🍴
20
12
  - Get metadata about your monorepo 🤖
21
13
  - Orchestrate your workspaces' `package.json` scripts 📋
22
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! 🛠️
23
16
 
24
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.
25
18
 
@@ -261,7 +254,8 @@ Workspace configs can be placed in a workspace's directory at `bw.workspace.ts`.
261
254
  ```typescript
262
255
  // bw.workspace.ts — place in a workspace directory
263
256
 
264
- // Also supported: bw.workspace.js, bw.workspace.json, bw.workspace.jsonc, or a "bw" key in package.json
257
+ // Also supported: bw.workspace.js, bw.workspace.json, bw.workspace.jsonc,
258
+ // or a "bw" key in package.json
265
259
 
266
260
  import { defineWorkspaceConfig } from "bun-workspaces/config";
267
261
 
@@ -295,7 +289,9 @@ which can also apply workspace configs in bulk by using workspace patterns.
295
289
 
296
290
  ```typescript
297
291
  // bw.root.ts — place in your project root directory
298
- // Also supported: bw.root.js, bw.root.json, bw.root.jsonc, or a "bw" key in package.json
292
+ // Also supported: bw.root.js, bw.root.json, bw.root.jsonc,
293
+ // or a "bw-root" key in package.json
294
+
299
295
  import { defineRootConfig } from "bun-workspaces/config";
300
296
 
301
297
  export default defineRootConfig({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-workspaces",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
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
@@ -138,7 +138,7 @@ const CLI_COMMANDS_CONFIG = {
138
138
  },
139
139
  mcpServer: {
140
140
  command: "mcp-server",
141
- isGlobal: false,
141
+ isGlobal: true,
142
142
  aliases: [],
143
143
  description:
144
144
  "Start the bun-workspaces MCP (Model Context Protocol) server over stdio",
@@ -1,4 +1,4 @@
1
- const SCRIPT_RUNTIME_METADATA_CONFIG = {
1
+ const WORKSPACE_SCRIPT_METADATA_CONFIG = {
2
2
  projectPath: {
3
3
  inlineName: "<projectPath>",
4
4
  envVarName: "BW_PROJECT_PATH",
@@ -24,14 +24,14 @@ const SCRIPT_RUNTIME_METADATA_CONFIG = {
24
24
  envVarName: "BW_WORKSPACE_NAME",
25
25
  },
26
26
  };
27
- const validateScriptRuntimeMetadataKey = (key) => {
28
- if (!(key in SCRIPT_RUNTIME_METADATA_CONFIG)) {
29
- throw new Error(`Invalid script runtime metadata key: ${key}`);
27
+ const validateWorkspaceScriptMetadataKey = (key) => {
28
+ if (!(key in WORKSPACE_SCRIPT_METADATA_CONFIG)) {
29
+ throw new Error(`Invalid workspace script metadata key: ${key}`);
30
30
  }
31
31
  };
32
- const getScriptRuntimeMetadataConfig = (key) => {
33
- validateScriptRuntimeMetadataKey(key);
34
- return SCRIPT_RUNTIME_METADATA_CONFIG[key];
32
+ const getWorkspaceScriptMetadataConfig = (key) => {
33
+ validateWorkspaceScriptMetadataKey(key);
34
+ return WORKSPACE_SCRIPT_METADATA_CONFIG[key];
35
35
  };
36
36
 
37
- export { getScriptRuntimeMetadataConfig };
37
+ export { getWorkspaceScriptMetadataConfig };
@@ -1,336 +1,46 @@
1
1
  import package_0 from "../../../package.json";
2
2
  import { createMcpServer } from "./core/index.mjs";
3
3
  import { registerBwResources } from "./resources.mjs";
4
+ import { setServerWorkingDirectory } from "./serverState.mjs";
4
5
  import { registerBwTools } from "./tools.mjs";
5
6
 
6
- const CLI_QUICKSTART = `
7
- # You can add this to .bashrc, .zshrc, or similar.
8
- # You can also invoke "bw" in your root package.json scripts.
9
- alias bw="bunx bun-workspaces"
10
-
11
- # List all workspaces in your project
12
- bw list-workspaces
13
-
14
- # ls is an alias for list-workspaces
15
- bw ls --json --pretty # Output as formatted JSON
16
-
17
- # Get info about a workspace
18
- bw workspace-info my-workspace
19
- bw info my-workspace --json --pretty # info is alias for workspace-info
20
-
21
- # Get info about a script, such as the workspaces that have it
22
- bw script-info my-script
23
-
24
- # Run the lint script for all workspaces
25
- # that have it in their package.json "scripts" field
26
- bw run-script lint
27
-
28
- # run is an alias for run-script
29
- bw run lint my-workspace # Run for a single workspace
30
- bw run lint my-workspace-a my-workspace-b # Run for multiple workspaces
31
- bw run lint my-alias-a my-alias-b # Run by alias (set by optional config)
32
-
33
- # A workspace's script will wait until any workspaces it depends on have completed
34
- # Similar to Bun's --filter behavior
35
- bw run lint --dep-order
36
-
37
- # Continue running scripts even if a dependency fails
38
- bw run lint --dep-order --ignore-dep-failure
39
-
40
- bw run lint "my-workspace-*" # Run for matching workspace names
41
- bw run lint "alias:my-alias-*" "path:my-glob/**/*" "tag:my-tag" # Use matching specifiers
42
- bw run lint "*" "not:path:my-path/*" # Run for all workspaces not in my-path/
43
-
44
- bw run lint --args="--my-appended-args" # Add args to each script call
45
- bw run lint --args="--my-arg=<workspaceName>" # Use the workspace name in args
46
-
47
- bw run "bun build" --inline # Run an inline command via the Bun shell
48
-
49
- # Scripts run in parallel by default
50
- bw run lint --parallel=false # Run in series
51
- bw run lint --parallel=2 # Run in parallel with a max of 2 concurrent scripts
52
- bw run lint --parallel=auto # Default, based on number of available logical CPUs
53
- bw run lint --parallel=50% # Run in parallel with a max of 50% of the "auto" limit
54
-
55
- # Use the grouped output style (default when on a TTY)
56
- bw run my-script --output-style=grouped
57
-
58
- # Set the max preview lines for script output in grouped output style
59
- bw run my-script --output-style=grouped --grouped-lines=auto
60
- bw run my-script --output-style=grouped --grouped-lines=10
61
-
62
- # Use simple script output with workspace prefixes (default when not on a TTY)
63
- bw run my-script --output-style=prefixed
64
-
65
- # Use the plain output style (no workspace prefixes)
66
- bw run my-script --output-style=plain
67
-
68
- # Silence all output of the run command
69
- bw --log-level=silent run my-script --output-style=none
70
-
71
- # Show usage (you can pass --help to any command)
72
- bw help
73
- bw --help
74
-
75
- # Show version
76
- bw --version
77
-
78
- # Pass --cwd to any command
79
- bw --cwd=/path/to/your/project ls
80
- bw --cwd=/path/to/your/project run my-script
81
-
82
- # Pass --log-level to any command (debug, info, warn, error, or silent)
83
- bw --log-level=debug ls
84
- `.trim();
85
- const INLINE_SCRIPT_EXAMPLE =
86
- /* unused pure expression or super */ null &&
87
- `
88
- # Run an inline command from the workspace directory
89
- bw run "bun run build" --inline
90
- `.trim();
91
-
92
- const RUN_WORKSPACE_SCRIPT_EXAMPLE = `
93
- const { output, exit } = project.runWorkspaceScript({
94
- workspaceNameOrAlias: "my-workspace",
95
- script: "my-script",
96
-
97
- // Optional. Arguments to add to the command
98
- // Can be a string or an array of strings
99
- // If string, the argv will be parsed POSIX-style
100
- args: ["--my", "--appended", "--args"],
101
-
102
- // Optional. Whether to ignore all output from the script.
103
- // This saves memory when you don't need script output.
104
- ignoreOutput: false,
105
- });
106
-
107
- // Get a stream of the script subprocess's output
108
- for await (const { chunk, metadata } of output.text()) {
109
- // console.log(chunk); // The output chunk's content (string)
110
- // console.log(metadata.streamName); // The output stream, "stdout" or "stderr"
111
- // console.log(metadata.workspace); // The target Workspace
112
- }
113
-
114
- // Get data about the script execution after it exits
115
- const exitResult = await exit;
116
-
117
- // exitResult.exitCode // The exit code (number)
118
- // exitResult.signal // The exit signal (string), or null
119
- // exitResult.success // true if exit code was 0
120
- // exitResult.startTimeISO // Start time (string)
121
- // exitResult.endTimeISO // End time (string)
122
- // exitResult.durationMs // Duration in milliseconds (number)
123
- // exitResult.metadata.workspace // The target workspace (Workspace)
124
-
125
- `.trim();
126
- const RUN_SCRIPT_ACROSS_WORKSPACES_EXAMPLE = `
127
-
128
- const { output, summary } = project.runScriptAcrossWorkspaces({
129
- // Optional. This will run in all matching workspaces that have my-script
130
- // Accepts same values as the CLI run-script command's workspace patterns
131
- // When not provided, all workspaces that have the script will be used.
132
- workspacePatterns: ["my-workspace", "my-name-pattern-*"],
133
-
134
- // Required. The package.json "scripts" field name to run
135
- script: "my-script",
136
-
137
- // Optional. Arguments to add to the command (same as for runWorkspaceScript)
138
- args: ["--my", "--appended", "--args"],
139
-
140
- // Optional. Whether to run the scripts in parallel (default: true)
141
- parallel: true,
142
-
143
- // Optional. When true, a workspace's script will wait
144
- // until any workspaces it depends on have completed
145
- dependencyOrder: false,
146
-
147
- // Optional. When true and dependencyOrder is true,
148
- // continue running scripts even if a dependency fails
149
- ignoreDependencyFailure: false,
150
-
151
- // Optional. Whether to ignore all output from the scripts.
152
- // This saves memory when you don't need script output.
153
- ignoreOutput: false,
154
-
155
- // Optional, callback when script starts, skips, or exits
156
- onScriptEvent: (event, { workspace, exitResult }) => {
157
- // event: "start", "skip", "exit"
158
- }
159
- });
160
-
161
- // Get a stream of script output
162
- for await (const { chunk, metadata } of output.text()) {
163
- // console.log(chunk); // the output chunk's content (string)
164
- // console.log(metadata.streamName); // "stdout" or "stderr"
165
- // console.log(metadata.workspace); // the Workspace that the output came from
166
- }
167
-
168
- // Get final summary data and script exit details after all scripts have completed
169
- const summaryResult = await summary;
170
-
171
- // summaryResult.totalCount // Total number of scripts
172
- // summaryResult.allSuccess // true if all scripts succeeded
173
- // summaryResult.successCount // Number of scripts that succeeded
174
- // summaryResult.failureCount // Number of scripts that failed
175
- // summaryResult.startTimeISO // Start time (string)
176
- // summaryResult.endTimeISO // End time (string)
177
- // summaryResult.durationMs // Total duration in milliseconds (number)
178
-
179
- // The exit details of each workspace script
180
- for (const exitResult of summaryResult.scriptResults) {
181
- // exitResult.exitCode // The exit code (number)
182
- // exitResult.signal // The exit signal (string), or null
183
- // exitResult.success // true if exit code was 0
184
- // exitResult.startTimeISO // Start time (ISO string)
185
- // exitResult.endTimeISO // End time (ISO string)
186
- // exitResult.durationMs // Duration in milliseconds (number)
187
- // exitResult.metadata.workspace // The target workspace (Workspace)
188
- }
189
- `.trim();
190
- const API_QUICKSTART = `
191
- import { createFileSystemProject } from "bun-workspaces";
192
-
193
- // A Project contains the core functionality of bun-workspaces.
194
- // Below defaults to process.cwd() for the project root directory
195
- // Pass { rootDirectory: "path/to/your/project" } to use a different root directory
196
- const project = createFileSystemProject();
197
-
198
- // A Workspace that matches the name or alias "my-workspace"
199
- const myWorkspace = project.findWorkspaceByNameOrAlias("my-workspace");
200
-
201
- // Array of workspaces whose names match the wildcard pattern
202
- const wildcardWorkspaces = project.findWorkspacesByPattern("my-workspace-*");
203
-
204
- // Array of workspaces that have "my-script" in their package.json "scripts"
205
- const workspacesWithScript = project.listWorkspacesWithScript("my-script");
206
-
207
- // Run a script in a workspace
208
- const runSingleScript = async () => {
209
- ${RUN_WORKSPACE_SCRIPT_EXAMPLE.split("\n").join("\n ")}
210
- }
211
-
212
- // Run a script in all workspaces that have it in their package.json "scripts" field
213
- const runManyScripts = async () => {
214
- ${RUN_SCRIPT_ACROSS_WORKSPACES_EXAMPLE.split("\n").join("\n ")}
215
- }
216
- `.trim();
217
-
218
- const ROOT_CONFIG_QUICKSTART = `
219
- // bw.root.ts — place in your project root directory
220
- // Also supported: bw.root.js, bw.root.json, bw.root.jsonc, or a "bw" key in package.json
221
- import { defineRootConfig } from "bun-workspaces/config";
222
-
223
- export default defineRootConfig({
224
- defaults: {
225
- // default value for --parallel option
226
- parallelMax: 4,
227
- // default value for --shell option
228
- shell: "system",
229
- // default value for global --include-root-workspace option
230
- includeRootWorkspace: false,
231
- },
232
-
233
- // Apply workspace configs in bulk by workspace pattern, in order.
234
- // Each entry merges into matching workspaces' accumulated config.
235
- // Pattern matching reflects aliases and tags added by earlier entries.
236
- workspacePatternConfigs: [
237
- {
238
- patterns: ["path:packages/apps/**/*"],
239
- config: { tags: ["app"] },
240
- },
241
- {
242
- patterns: ["path:packages/libs/**/*"],
243
- config: { tags: ["lib"] },
244
- },
245
- {
246
- // "tag:app" matches because the first entry added it
247
- patterns: ["tag:app"],
248
- config: {
249
- rules: {
250
- workspaceDependencies: {
251
- allowPatterns: ["tag:lib"], // apps may only depend on libs
252
- },
253
- },
254
- },
255
- },
256
- {
257
- patterns: ["tag:app"],
258
- // Factory form: receives static workspace data and accumulated config
259
- config: (workspace, prevConfig) => ({
260
- alias: workspace.name.replace(/^@my-scope\\//, ""),
261
- }),
262
- },
263
- ],
264
- });
265
- `.trim();
266
- const WORKSPACE_CONFIG_QUICKSTART = `
267
- // bw.workspace.ts — place in a workspace directory
268
-
269
- // Also supported: bw.workspace.js, bw.workspace.json, bw.workspace.jsonc, or a "bw" key in package.json
270
-
271
- import { defineWorkspaceConfig } from "bun-workspaces/config";
272
-
273
- export default defineWorkspaceConfig({
274
- alias: "my-web-app", // shorthand name; use array for multiple
275
- tags: ["app", "frontend"],
276
- scripts: {
277
- // lower order runs first in sequenced script execution
278
- build: { order: 1 },
279
- test: { order: 2 },
280
- },
281
- rules: {
282
- workspaceDependencies: {
283
- // Only "my-workspace" or workspaces tagged "lib" are allowed as dependencies
284
- allowPatterns: ["tag:lib", "my-workspace"],
285
- // Workspaces tagged "backend" are forbidden as dependencies
286
- denyPatterns: ["tag:backend"],
287
- },
288
- },
289
- });
290
- `.trim();
291
-
292
7
  const SERVER_INSTRUCTIONS = `
293
- bun-workspaces MCP server: tools to query Bun monorepo workspace metadata and documentation resources for the bun-workspaces CLI and TypeScript API.
8
+ bun-workspaces ${package_0.version} MCP server: tools to query Bun monorepo workspace metadata and documentation resources for the bun-workspaces CLI and TypeScript API.
294
9
 
295
- bun-workspaces is an npm package that works on top of Bun's native workspaces. If this server is running, the project likely has bun-workspaces installed, or the user invokes it via bunx — often using the recommended alias "bw" for \`bunx bun-workspaces\`.
10
+ bun-workspaces is an npm package that works on top of Bun's native workspaces. It has a CLI and TS API.
296
11
 
297
- Use the tools to understand the project's workspaces and scripts. Running scripts across workspaces is a core bw feature not exposed as a tool — use the CLI directly. See the bw://docs/cli resource for the full CLI reference.
12
+ Files such as bw.workspace.ts and bw.root.ts may be present for configuration.
298
13
 
299
- There are optional configuration files for the bun-workspaces CLI and TypeScript API. See the bw://docs/config resource for the full configuration reference.
14
+ Use resources for docs on the CLI and TS API, or get a project overview via bw://project.
15
+ bw://docs/overview, bw://docs/concepts, bw://docs/cli, bw://docs/api, and bw://docs/config cover most functionality.
300
16
 
301
- ## CLI quickstart
17
+ Use the tools to get specific metadata about the project.
302
18
 
19
+ ## CLI quickstart
303
20
  \`\`\`bash
304
- ${CLI_QUICKSTART}
21
+ $ alias bw="bunx bun-workspaces"
22
+ $ bw --help # usage
23
+ $ # run is an alias for run-script
24
+ $ bw run lint # run the "lint" script for all workspaces that have it
25
+ $ bw run "echo inline script" --inline # run an inline command via the Bun shell
26
+ $ bw run lint my-workspace-a my-workspace-b # run for specific workspaces
27
+ $ bw run lint --dep-order # run the lint script for all workspaces, waiting for all dependencies to complete
28
+ $ bw run lint "my-workspace-*" # wildcard for workspace names
29
+ $ bw run lint "alias:my-alias-*" "path:packages/**/*" "not:path:my-path/*" # use workspace patterns
305
30
  \`\`\`
306
31
 
307
- ## API quickstart
308
-
309
- \`\`\`typescript
310
- ${API_QUICKSTART}
311
- \`\`\`
312
-
313
- ## Root config quickstart
314
-
315
- \`\`\`typescript
316
- ${ROOT_CONFIG_QUICKSTART}
317
- \`\`\`
318
-
319
- ## Workspace config quickstart
320
-
321
- \`\`\`typescript
322
- ${WORKSPACE_CONFIG_QUICKSTART}
323
- \`\`\`
32
+ (end bun-workspaces MCP instructions)
324
33
  `.trim();
325
- const startBwMcpServer = async (project) => {
34
+ const startBwMcpServer = async (options) => {
35
+ setServerWorkingDirectory(options.initialWorkingDirectory);
326
36
  const server = createMcpServer({
327
37
  name: "bun-workspaces",
328
38
  version: package_0.version,
329
39
  instructions: SERVER_INSTRUCTIONS,
330
40
  });
331
- registerBwTools(server, project);
332
- registerBwResources(server, project);
41
+ registerBwTools(server);
42
+ registerBwResources(server);
333
43
  await server.start();
334
44
  };
335
45
 
336
- export { startBwMcpServer };
46
+ export { SERVER_INSTRUCTIONS, startBwMcpServer };
@@ -5,6 +5,7 @@ import {
5
5
  DOC_CONFIG,
6
6
  DOC_OVERVIEW,
7
7
  } from "../../internal/generated/aiDocs/docs.mjs";
8
+ import { getServerProject } from "./serverState.mjs";
8
9
 
9
10
  const textResource = (uri, text) => ({
10
11
  contents: [
@@ -15,7 +16,7 @@ const textResource = (uri, text) => ({
15
16
  },
16
17
  ],
17
18
  });
18
- const registerBwResources = (server, project) => {
19
+ const registerBwResources = (server) => {
19
20
  server.registerResource(
20
21
  {
21
22
  uri: "bw://project",
@@ -24,23 +25,30 @@ const registerBwResources = (server, project) => {
24
25
  "Overview of this bun-workspaces project: name, root directory, and all workspace metadata.",
25
26
  mimeType: "application/json",
26
27
  },
27
- (uri) => ({
28
- contents: [
29
- {
30
- uri,
31
- mimeType: "application/json",
32
- text: JSON.stringify(
33
- {
34
- name: project.name,
35
- rootDirectory: project.rootDirectory,
36
- workspaces: project.workspaces,
37
- },
38
- null,
39
- 2,
40
- ),
41
- },
42
- ],
43
- }),
28
+ (uri) => {
29
+ const project = getServerProject();
30
+ const data = project
31
+ ? {
32
+ available: true,
33
+ name: project.name,
34
+ rootDirectory: project.rootDirectory,
35
+ workspaces: project.workspaces,
36
+ }
37
+ : {
38
+ available: false,
39
+ message:
40
+ "No bun-workspaces project is available in the current directory.",
41
+ };
42
+ return {
43
+ contents: [
44
+ {
45
+ uri,
46
+ mimeType: "application/json",
47
+ text: JSON.stringify(data, null, 2),
48
+ },
49
+ ],
50
+ };
51
+ },
44
52
  );
45
53
  server.registerResource(
46
54
  {
@@ -57,7 +65,7 @@ const registerBwResources = (server, project) => {
57
65
  uri: "bw://docs/concepts",
58
66
  name: "bun-workspaces concepts",
59
67
  description:
60
- "Workspace patterns, script runtime metadata, and how to run scripts via the CLI.",
68
+ "Workspace patterns, workspace script metadata, and how to run scripts via the CLI.",
61
69
  mimeType: "text/markdown",
62
70
  },
63
71
  (uri) => textResource(uri, DOC_CONCEPTS),
@@ -0,0 +1,20 @@
1
+ import { createFileSystemProject } from "../../project/implementations/fileSystemProject.mjs";
2
+
3
+ const SERVER_STATE = {
4
+ workingDirectory: null,
5
+ };
6
+ const setServerWorkingDirectory = (directory) => {
7
+ SERVER_STATE.workingDirectory = directory;
8
+ };
9
+ const getServerProject = () => {
10
+ if (!SERVER_STATE.workingDirectory) return null;
11
+ try {
12
+ return createFileSystemProject({
13
+ rootDirectory: SERVER_STATE.workingDirectory,
14
+ });
15
+ } catch {
16
+ return null;
17
+ }
18
+ };
19
+
20
+ export { getServerProject, setServerWorkingDirectory };
@@ -1,5 +1,6 @@
1
1
  import { getDoctorInfo } from "../../doctor/index.mjs";
2
2
  import { BUN_WORKSPACES_VERSION } from "../../internal/version.mjs";
3
+ import { getServerProject, setServerWorkingDirectory } from "./serverState.mjs";
3
4
 
4
5
  const textResult = (data) => ({
5
6
  content: [
@@ -18,7 +19,14 @@ const errorResult = (message) => ({
18
19
  ],
19
20
  isError: true,
20
21
  });
21
- const registerBwTools = (server, project) => {
22
+ const NO_PROJECT_RESULT = errorResult(
23
+ "No bun-workspaces project is available in the current directory.",
24
+ );
25
+ const withProject = (handler) => (input) => {
26
+ const project = getServerProject();
27
+ return project ? handler(project, input) : NO_PROJECT_RESULT;
28
+ };
29
+ const registerBwTools = (server) => {
22
30
  server.registerTool(
23
31
  {
24
32
  name: "version",
@@ -51,13 +59,13 @@ const registerBwTools = (server, project) => {
51
59
  },
52
60
  },
53
61
  },
54
- ({ patterns }) => {
62
+ withProject((project, { patterns }) => {
55
63
  const workspaces =
56
64
  patterns && Array.isArray(patterns) && patterns.length > 0
57
65
  ? project.findWorkspacesByPattern(...patterns)
58
66
  : project.workspaces;
59
67
  return textResult(workspaces);
60
- },
68
+ }),
61
69
  );
62
70
  server.registerTool(
63
71
  {
@@ -76,7 +84,7 @@ const registerBwTools = (server, project) => {
76
84
  required: ["nameOrAlias"],
77
85
  },
78
86
  },
79
- ({ nameOrAlias }) => {
87
+ withProject((project, { nameOrAlias }) => {
80
88
  const name = nameOrAlias;
81
89
  const workspace =
82
90
  name === /* inlined export .ROOT_WORKSPACE_SELECTOR */ "@root"
@@ -86,7 +94,7 @@ const registerBwTools = (server, project) => {
86
94
  return errorResult(`Workspace not found: "${name}"`);
87
95
  }
88
96
  return textResult(workspace);
89
- },
97
+ }),
90
98
  );
91
99
  server.registerTool(
92
100
  {
@@ -96,7 +104,7 @@ const registerBwTools = (server, project) => {
96
104
  type: "object",
97
105
  },
98
106
  },
99
- () => textResult(project.rootWorkspace),
107
+ withProject((project) => textResult(project.rootWorkspace)),
100
108
  );
101
109
  server.registerTool(
102
110
  {
@@ -107,14 +115,14 @@ const registerBwTools = (server, project) => {
107
115
  type: "object",
108
116
  },
109
117
  },
110
- () => {
118
+ withProject((project) => {
111
119
  const scriptMap = project.mapScriptsToWorkspaces();
112
120
  const scripts = Object.values(scriptMap).map(({ name, workspaces }) => ({
113
121
  name,
114
122
  workspaces: workspaces.map((w) => w.name),
115
123
  }));
116
124
  return textResult(scripts);
117
- },
125
+ }),
118
126
  );
119
127
  server.registerTool(
120
128
  {
@@ -132,7 +140,7 @@ const registerBwTools = (server, project) => {
132
140
  required: ["script"],
133
141
  },
134
142
  },
135
- ({ script }) => {
143
+ withProject((project, { script }) => {
136
144
  const scriptMap = project.mapScriptsToWorkspaces();
137
145
  const scriptMetadata = scriptMap[script];
138
146
  if (!scriptMetadata) {
@@ -142,7 +150,7 @@ const registerBwTools = (server, project) => {
142
150
  name: scriptMetadata.name,
143
151
  workspaces: scriptMetadata.workspaces.map((w) => w.name),
144
152
  });
145
- },
153
+ }),
146
154
  );
147
155
  server.registerTool(
148
156
  {
@@ -153,14 +161,14 @@ const registerBwTools = (server, project) => {
153
161
  type: "object",
154
162
  },
155
163
  },
156
- () => {
164
+ withProject((project) => {
157
165
  const tagMap = project.mapTagsToWorkspaces();
158
166
  const tags = Object.entries(tagMap).map(([tag, workspaces]) => ({
159
167
  tag,
160
168
  workspaces: workspaces.map((w) => w.name),
161
169
  }));
162
170
  return textResult(tags);
163
- },
171
+ }),
164
172
  );
165
173
  server.registerTool(
166
174
  {
@@ -178,7 +186,7 @@ const registerBwTools = (server, project) => {
178
186
  required: ["tag"],
179
187
  },
180
188
  },
181
- ({ tag }) => {
189
+ withProject((project, { tag }) => {
182
190
  const tagMap = project.mapTagsToWorkspaces();
183
191
  const tagWorkspaces = tagMap[tag];
184
192
  if (!tagWorkspaces) {
@@ -188,6 +196,36 @@ const registerBwTools = (server, project) => {
188
196
  name: tag,
189
197
  workspaces: tagWorkspaces.map((w) => w.name),
190
198
  });
199
+ }),
200
+ );
201
+ server.registerTool(
202
+ {
203
+ name: "set_working_directory",
204
+ description:
205
+ "Set the working directory used by this MCP server. All subsequent project queries will reflect the new directory.",
206
+ inputSchema: {
207
+ type: "object",
208
+ properties: {
209
+ directory: {
210
+ type: "string",
211
+ description: "Absolute path to the new working directory",
212
+ },
213
+ },
214
+ required: ["directory"],
215
+ },
216
+ },
217
+ ({ directory }) => {
218
+ setServerWorkingDirectory(directory);
219
+ const project = getServerProject();
220
+ return textResult({
221
+ directory,
222
+ project: project
223
+ ? {
224
+ name: project.name,
225
+ workspaces: project.workspaces.map((w) => w.name),
226
+ }
227
+ : null,
228
+ });
191
229
  },
192
230
  );
193
231
  server.registerTool(
@@ -1,10 +1,15 @@
1
1
  import { startBwMcpServer } from "../../ai/mcp/index.mjs";
2
2
  import { logger } from "../../internal/logger/index.mjs";
3
- import { handleProjectCommand } from "./commandHandlerUtils.mjs";
3
+ import { handleGlobalCommand } from "./commandHandlerUtils.mjs";
4
4
 
5
- const mcpServer = handleProjectCommand("mcpServer", async ({ project }) => {
6
- logger.printLevel = "silent";
7
- await startBwMcpServer(project);
8
- });
5
+ const mcpServer = handleGlobalCommand(
6
+ "mcpServer",
7
+ async ({ workingDirectory }) => {
8
+ logger.printLevel = "silent";
9
+ await startBwMcpServer({
10
+ initialWorkingDirectory: workingDirectory,
11
+ });
12
+ },
13
+ );
9
14
 
10
15
  export { mcpServer };
@@ -82,11 +82,8 @@ const createCli = ({ defaultCwd = process.cwd(), defaultMiddleware } = {}) => {
82
82
  process.exit(1);
83
83
  return;
84
84
  }
85
- const { project, projectError } = initializeWithGlobalOptions(
86
- program,
87
- args,
88
- middleware,
89
- );
85
+ const { project, projectError, workingDirectory } =
86
+ initializeWithGlobalOptions(program, args, middleware);
90
87
  middleware.findProject({
91
88
  ...defaultContext,
92
89
  project,
@@ -105,6 +102,7 @@ const createCli = ({ defaultCwd = process.cwd(), defaultMiddleware } = {}) => {
105
102
  outputWriters,
106
103
  terminalWidth,
107
104
  terminalHeight,
105
+ workingDirectory,
108
106
  });
109
107
  defineGlobalCommands({
110
108
  program,
@@ -113,6 +111,7 @@ const createCli = ({ defaultCwd = process.cwd(), defaultMiddleware } = {}) => {
113
111
  outputWriters,
114
112
  terminalWidth,
115
113
  terminalHeight,
114
+ workingDirectory,
116
115
  });
117
116
  logger.debug(`Commands initialized. Parsing args...`);
118
117
  middleware.preParse({
@@ -133,6 +133,7 @@ const applyGlobalOptions = (options) => {
133
133
  return {
134
134
  project,
135
135
  projectError: error,
136
+ workingDirectory: options.cwd,
136
137
  };
137
138
  };
138
139
  const initializeWithGlobalOptions = (program, args, middleware) => {
@@ -587,7 +587,7 @@ export declare const CLI_COMMANDS_CONFIG: {
587
587
  };
588
588
  readonly mcpServer: {
589
589
  readonly command: "mcp-server";
590
- readonly isGlobal: false;
590
+ readonly isGlobal: true;
591
591
  readonly aliases: [];
592
592
  readonly description: "Start the bun-workspaces MCP (Model Context Protocol) server over stdio";
593
593
  readonly options: {};
@@ -791,7 +791,7 @@ export declare const getCliCommandConfig: (commandName: CliCommandName) =>
791
791
  }
792
792
  | {
793
793
  readonly command: "mcp-server";
794
- readonly isGlobal: false;
794
+ readonly isGlobal: true;
795
795
  readonly aliases: [];
796
796
  readonly description: "Start the bun-workspaces MCP (Model Context Protocol) server over stdio";
797
797
  readonly options: {};
@@ -919,6 +919,7 @@ export type GlobalCommandContext = {
919
919
  outputWriters: Required<WriteOutputOptions>;
920
920
  terminalWidth: number;
921
921
  terminalHeight: number;
922
+ workingDirectory: string;
922
923
  };
923
924
  export type ProjectCommandContext = GlobalCommandContext & {
924
925
  project: FileSystemProject;
@@ -1022,7 +1023,7 @@ export declare const defineProjectCommands: (
1022
1023
  context: ProjectCommandContext,
1023
1024
  ) => void;
1024
1025
  export declare const mcpServer: (
1025
- context: ProjectCommandContext,
1026
+ context: GlobalCommandContext,
1026
1027
  ) => import("commander").Command;
1027
1028
  export declare const runScript: (
1028
1029
  context: ProjectCommandContext,
@@ -1080,6 +1081,7 @@ export declare const initializeWithGlobalOptions: (
1080
1081
  ): CreateProjectScriptCommandResult;
1081
1082
  }>;
1082
1083
  projectError: Error | null;
1084
+ workingDirectory: string;
1083
1085
  };
1084
1086
 
1085
1087
  export {};
@@ -25,12 +25,28 @@ Patterns can include a wildcard to match only by workspace name: \`my-workspace-
25
25
  - Special root workspace selector: \`@root\`.
26
26
  - 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.
27
27
 
28
- ### Script runtime metadata
28
+ ### Workspace Script Metadata
29
29
 
30
- Scripts ran via bw can access metadata via env vars. This same metadata can be interpolated into inline scripts and appended args.
30
+ Scripts ran via bun-workspaces can access metadata about the workspace, script, and project
31
+ via env vars. This same metadata can also be interpolated into inline scripts and appended args.
31
32
 
32
33
  \`\`\`typescript
33
- // in a script
34
+ // in a workspace's script invoked by bun-workspaces using a metadata function
35
+ import { getWorkspaceScriptMetadata } from "bun-workspaces/script";
36
+
37
+ // Use the helper within a script that was invoked via bun-workspaces
38
+ const projectPath = getWorkspaceScriptMetadata("projectPath");
39
+ const projectName = getWorkspaceScriptMetadata("projectName");
40
+ const workspaceName = getWorkspaceScriptMetadata("workspaceName");
41
+ const workspacePath = getWorkspaceScriptMetadata("workspacePath");
42
+ const workspaceRelativePath = getWorkspaceScriptMetadata(
43
+ "workspaceRelativePath",
44
+ );
45
+ const scriptName = getWorkspaceScriptMetadata("scriptName");
46
+ \`\`\`
47
+
48
+ \`\`\`typescript
49
+ // In a script, but accessing the same data via plain environment variables (same values as previous example)
34
50
  const projectPath = process.env.BW_PROJECT_PATH;
35
51
  const workspaceName = process.env.BW_WORKSPACE_NAME;
36
52
  const workspacePath = process.env.BW_WORKSPACE_PATH;
@@ -15,7 +15,7 @@ import {
15
15
  import { logger } from "../../internal/logger/index.mjs";
16
16
  import {
17
17
  createScriptRuntimeEnvVars,
18
- interpolateScriptRuntimeMetadata,
18
+ interpolateWorkspaceScriptMetadata,
19
19
  runScript,
20
20
  runScripts,
21
21
  } from "../../runScript/index.mjs";
@@ -39,11 +39,18 @@ const serializeArgs = (args, metadata, shell) => {
39
39
  if (Array.isArray(args)) {
40
40
  return args
41
41
  .map((arg) =>
42
- quoteArg(interpolateScriptRuntimeMetadata(arg, metadata, shell), shell),
42
+ quoteArg(
43
+ interpolateWorkspaceScriptMetadata(arg, metadata, shell),
44
+ shell,
45
+ ),
43
46
  )
44
47
  .join(" ");
45
48
  }
46
- const interpolated = interpolateScriptRuntimeMetadata(args, metadata, shell);
49
+ const interpolated = interpolateWorkspaceScriptMetadata(
50
+ args,
51
+ metadata,
52
+ shell,
53
+ );
47
54
  // Escape backslashes in interpolated values before POSIX parse on Windows,
48
55
  // so that path separators survive parse's escape processing (\\→\)
49
56
  const parseInput =
@@ -215,7 +222,7 @@ class _FileSystemProject extends ProjectBase {
215
222
  typeof options.inline === "object"
216
223
  ? (options.inline?.scriptName ?? "")
217
224
  : "";
218
- const scriptRuntimeMetadata = {
225
+ const workspaceScriptMetadata = {
219
226
  projectPath: this.rootDirectory,
220
227
  projectName: this.name,
221
228
  workspacePath: resolveWorkspacePath(this, workspace),
@@ -223,11 +230,11 @@ class _FileSystemProject extends ProjectBase {
223
230
  workspaceName: workspace.name,
224
231
  scriptName: options.inline ? inlineScriptName : options.script,
225
232
  };
226
- const args = serializeArgs(options.args, scriptRuntimeMetadata, shell);
233
+ const args = serializeArgs(options.args, workspaceScriptMetadata, shell);
227
234
  const script = options.inline
228
- ? interpolateScriptRuntimeMetadata(
235
+ ? interpolateWorkspaceScriptMetadata(
229
236
  options.script,
230
- scriptRuntimeMetadata,
237
+ workspaceScriptMetadata,
231
238
  shell,
232
239
  ) + (args ? " " + args : "")
233
240
  : options.script;
@@ -251,7 +258,7 @@ class _FileSystemProject extends ProjectBase {
251
258
  metadata: {
252
259
  workspace,
253
260
  },
254
- env: createScriptRuntimeEnvVars(scriptRuntimeMetadata),
261
+ env: createScriptRuntimeEnvVars(workspaceScriptMetadata),
255
262
  shell,
256
263
  ignoreOutput: options.ignoreOutput ?? false,
257
264
  });
@@ -422,7 +429,7 @@ class _FileSystemProject extends ProjectBase {
422
429
  typeof options.inline === "object"
423
430
  ? (options.inline?.scriptName ?? "")
424
431
  : "";
425
- const scriptRuntimeMetadata = {
432
+ const workspaceScriptMetadata = {
426
433
  projectPath: this.rootDirectory,
427
434
  projectName: this.name,
428
435
  workspacePath: resolveWorkspacePath(this, workspace),
@@ -430,11 +437,15 @@ class _FileSystemProject extends ProjectBase {
430
437
  workspaceName: workspace.name,
431
438
  scriptName: options.inline ? inlineScriptName : options.script,
432
439
  };
433
- const args = serializeArgs(options.args, scriptRuntimeMetadata, shell);
440
+ const args = serializeArgs(
441
+ options.args,
442
+ workspaceScriptMetadata,
443
+ shell,
444
+ );
434
445
  const script = options.inline
435
- ? interpolateScriptRuntimeMetadata(
446
+ ? interpolateWorkspaceScriptMetadata(
436
447
  options.script,
437
- scriptRuntimeMetadata,
448
+ workspaceScriptMetadata,
438
449
  shell,
439
450
  ) + (args ? " " + args : "")
440
451
  : options.script;
@@ -456,7 +467,7 @@ class _FileSystemProject extends ProjectBase {
456
467
  workspace,
457
468
  },
458
469
  scriptCommand,
459
- env: createScriptRuntimeEnvVars(scriptRuntimeMetadata),
470
+ env: createScriptRuntimeEnvVars(workspaceScriptMetadata),
460
471
  shell,
461
472
  dependsOn: options.dependencyOrder
462
473
  ? workspace.dependencies
@@ -3,7 +3,7 @@ export * from "./runScripts.mjs";
3
3
  export * from "./scriptCommand.mjs";
4
4
  export * from "./output/index.mjs";
5
5
  export * from "./parallel.mjs";
6
- export * from "./scriptRuntimeMetadata.mjs";
6
+ export * from "./workspaceScriptMetadata.mjs";
7
7
  export * from "./scriptShellOption.mjs";
8
8
 
9
9
  export {};
@@ -4,11 +4,7 @@ import {
4
4
  defineErrors,
5
5
  } from "../../internal/core/index.mjs";
6
6
 
7
- const ERRORS = defineErrors(
8
- BunWorkspacesError,
9
- "OutputStreamStarted",
10
- "OutputStreamDone",
11
- );
7
+ const ERRORS = defineErrors(BunWorkspacesError, "OutputStreamStarted");
12
8
  class _ProcessOutput {
13
9
  constructor(stream, metadata) {
14
10
  this.#inputStream = stream;
@@ -95,11 +91,6 @@ class _ProcessOutput {
95
91
  return this.#error;
96
92
  }
97
93
  #onStart() {
98
- if (this.#isDone) {
99
- throw new ERRORS.OutputStreamDone(
100
- "The output stream has already been closed.",
101
- );
102
- }
103
94
  if (this.#isStarted) {
104
95
  throw new ERRORS.OutputStreamStarted(
105
96
  "Only one stream can be opened via .bytes() or .text(). This stream has already been opened.",
@@ -1,6 +1,6 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
- declare const SCRIPT_RUNTIME_METADATA_CONFIG: {
3
+ declare const WORKSPACE_SCRIPT_METADATA_CONFIG: {
4
4
  readonly projectPath: {
5
5
  readonly inlineName: "<projectPath>";
6
6
  readonly envVarName: "BW_PROJECT_PATH";
@@ -26,15 +26,17 @@ declare const SCRIPT_RUNTIME_METADATA_CONFIG: {
26
26
  readonly envVarName: "BW_WORKSPACE_NAME";
27
27
  };
28
28
  };
29
- export type ScriptRuntimeMetadataKey =
30
- keyof typeof SCRIPT_RUNTIME_METADATA_CONFIG;
29
+ export type WorkspaceScriptMetadataKey =
30
+ keyof typeof WORKSPACE_SCRIPT_METADATA_CONFIG;
31
31
  /**
32
32
  * This is a utility to run from a workspace's script that was called via `bun-workspaces`.
33
33
  *
34
34
  * It gets the value of some metadata value about the project, workspace, or script that was invoked.
35
35
  */
36
36
  export declare const getWorkspaceScriptMetadata: (
37
- key: ScriptRuntimeMetadataKey,
37
+ key: WorkspaceScriptMetadataKey,
38
38
  ) => string;
39
+ /** @deprecated Renamed: Use {@link WorkspaceScriptMetadataKey} instead */
40
+ export type ScriptRuntimeMetadataKey = WorkspaceScriptMetadataKey;
39
41
 
40
42
  export {};
@@ -1 +1 @@
1
- export { getWorkspaceScriptMetadata } from "./scriptRuntimeMetadata.mjs";
1
+ export { getWorkspaceScriptMetadata } from "./workspaceScriptMetadata.mjs";
@@ -1,10 +1,10 @@
1
- import { getScriptRuntimeMetadataConfig } from "../1108.mjs";
1
+ import { getWorkspaceScriptMetadataConfig } from "../3725.mjs";
2
2
 
3
3
  const checkIsRecursiveScript = (workspaceName, scriptName) => {
4
4
  const parentWorkspace =
5
- process.env[getScriptRuntimeMetadataConfig("workspaceName").envVarName];
5
+ process.env[getWorkspaceScriptMetadataConfig("workspaceName").envVarName];
6
6
  const parentScript =
7
- process.env[getScriptRuntimeMetadataConfig("scriptName").envVarName];
7
+ process.env[getWorkspaceScriptMetadataConfig("scriptName").envVarName];
8
8
  if (!parentWorkspace || !parentScript) {
9
9
  return false;
10
10
  }
@@ -1,5 +1,5 @@
1
1
  import { BunWorkspacesError, IS_WINDOWS } from "../internal/core/index.mjs";
2
- import { getScriptRuntimeMetadataConfig } from "../1108.mjs";
2
+ import { getWorkspaceScriptMetadataConfig } from "../3725.mjs";
3
3
 
4
4
  const createScriptRuntimeEnvVars = (metadata) => {
5
5
  const keys = [
@@ -11,12 +11,12 @@ const createScriptRuntimeEnvVars = (metadata) => {
11
11
  "workspaceName",
12
12
  ];
13
13
  return keys.reduce((acc, key) => {
14
- const { envVarName } = getScriptRuntimeMetadataConfig(key);
14
+ const { envVarName } = getWorkspaceScriptMetadataConfig(key);
15
15
  acc[envVarName] = metadata[key];
16
16
  return acc;
17
17
  }, {});
18
18
  };
19
- const interpolateScriptRuntimeMetadata = (text, metadata, shell) => {
19
+ const interpolateWorkspaceScriptMetadata = (text, metadata, shell) => {
20
20
  const keys = [
21
21
  "projectPath",
22
22
  "projectName",
@@ -26,11 +26,11 @@ const interpolateScriptRuntimeMetadata = (text, metadata, shell) => {
26
26
  "workspaceName",
27
27
  ];
28
28
  const inlineNames = keys.map(
29
- (key) => getScriptRuntimeMetadataConfig(key).inlineName,
29
+ (key) => getWorkspaceScriptMetadataConfig(key).inlineName,
30
30
  );
31
31
  return text.replace(new RegExp(inlineNames.join("|"), "g"), (match) => {
32
32
  const key = keys.find(
33
- (k) => getScriptRuntimeMetadataConfig(k).inlineName === match,
33
+ (k) => getWorkspaceScriptMetadataConfig(k).inlineName === match,
34
34
  );
35
35
  const value = metadata[key];
36
36
  if (IS_WINDOWS && shell === "bun") {
@@ -44,7 +44,7 @@ const interpolateScriptRuntimeMetadata = (text, metadata, shell) => {
44
44
  *
45
45
  * It gets the value of some metadata value about the project, workspace, or script that was invoked.
46
46
  */ const getWorkspaceScriptMetadata = (key) => {
47
- const { envVarName } = getScriptRuntimeMetadataConfig(key);
47
+ const { envVarName } = getWorkspaceScriptMetadataConfig(key);
48
48
  if (!(envVarName in process.env)) {
49
49
  throw new BunWorkspacesError(
50
50
  `getScriptMetadata() called with key "${key}" but environment variable ${envVarName} is not set. getScriptMetadata() may not have been called in a workspace script running via bun-workspaces.`,
@@ -56,5 +56,5 @@ const interpolateScriptRuntimeMetadata = (text, metadata, shell) => {
56
56
  export {
57
57
  createScriptRuntimeEnvVars,
58
58
  getWorkspaceScriptMetadata,
59
- interpolateScriptRuntimeMetadata,
59
+ interpolateWorkspaceScriptMetadata,
60
60
  };