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
@@ -8,10 +8,15 @@ const GLOB_CHARACTER_REGEX = /[*?[{]/;
8
8
  const toPosixPath = (filePath) => filePath.replaceAll("\\", "/");
9
9
  const stripTrailingSlashes = (filePath) => filePath.replace(/\/+$/, "");
10
10
  const stripLeadingSlashes = (filePath) => filePath.replace(/^\/+/, "");
11
+ const stripDotSlashSegments = (filePath) => {
12
+ let stripped = filePath;
13
+ while (stripped.startsWith("./")) stripped = stripped.slice(2);
14
+ return stripped === "." ? "" : stripped;
15
+ };
11
16
  const normalizeChangedFilePath = ({ rootDirectory, filePath }) => {
12
17
  const posixFilePath = toPosixPath(filePath);
13
18
  if (!path.isAbsolute(filePath)) {
14
- return posixFilePath;
19
+ return stripDotSlashSegments(posixFilePath);
15
20
  }
16
21
  const posixRoot = stripTrailingSlashes(toPosixPath(rootDirectory));
17
22
  if (posixFilePath === posixRoot) {
@@ -160,12 +165,36 @@ const resolveInputWorkspaceDependencies = ({ workspaceInputs }) => {
160
165
  }
161
166
  return inputDependenciesByName;
162
167
  };
168
+ const collectDirectEdges = ({
169
+ workspace,
170
+ inputDependenciesByName,
171
+ ignoreWorkspaceDependencies,
172
+ }) => {
173
+ const edges = [];
174
+ for (const dependencyName of inputDependenciesByName.get(workspace.name) ??
175
+ []) {
176
+ edges.push({
177
+ dependencyName,
178
+ edgeSource: "input",
179
+ });
180
+ }
181
+ if (!ignoreWorkspaceDependencies) {
182
+ for (const dependencyName of workspace.dependencies) {
183
+ edges.push({
184
+ dependencyName,
185
+ edgeSource: "package",
186
+ });
187
+ }
188
+ }
189
+ return edges;
190
+ };
163
191
  const computeAffectedWorkspaceSet = ({
164
192
  workspaceInputs,
165
193
  workspaceByName,
166
194
  changedFilesByName,
195
+ externalDepChangesByWorkspace,
167
196
  inputDependenciesByName,
168
- ignorePackageDependencies,
197
+ ignoreWorkspaceDependencies,
169
198
  }) => {
170
199
  const inputDependentsByName = new Map();
171
200
  for (const [workspaceName, dependencyNames] of inputDependenciesByName) {
@@ -181,7 +210,11 @@ const computeAffectedWorkspaceSet = ({
181
210
  const affected = new Set();
182
211
  const queue = [];
183
212
  for (const { workspace } of workspaceInputs) {
184
- if ((changedFilesByName.get(workspace.name)?.length ?? 0) > 0) {
213
+ const hasChangedFiles =
214
+ (changedFilesByName.get(workspace.name)?.length ?? 0) > 0;
215
+ const hasExternalDepChanges =
216
+ (externalDepChangesByWorkspace.get(workspace.name)?.length ?? 0) > 0;
217
+ if (hasChangedFiles || hasExternalDepChanges) {
185
218
  affected.add(workspace.name);
186
219
  queue.push(workspace.name);
187
220
  }
@@ -191,7 +224,7 @@ const computeAffectedWorkspaceSet = ({
191
224
  const currentWorkspace = workspaceByName.get(currentName);
192
225
  const dependents = [
193
226
  ...(inputDependentsByName.get(currentName) ?? []),
194
- ...(!ignorePackageDependencies && currentWorkspace
227
+ ...(!ignoreWorkspaceDependencies && currentWorkspace
195
228
  ? currentWorkspace.dependents
196
229
  : []),
197
230
  ];
@@ -204,66 +237,117 @@ const computeAffectedWorkspaceSet = ({
204
237
  }
205
238
  return affected;
206
239
  };
240
+ /**
241
+ * Walk forward from `directDependencyName` through the affected dep graph,
242
+ * appending each next affected dep edge to the chain until we run out of
243
+ * affected dep edges to follow. Stops on:
244
+ * - no further affected dep edges,
245
+ * - revisiting a workspace already in the chain (cycle).
246
+ *
247
+ * Branching is broken deterministically by edge insertion order
248
+ * (input edges before package edges, declaration order within each).
249
+ */ const extendChainThroughAffectedDeps = ({
250
+ startingWorkspaceName,
251
+ directDependencyName,
252
+ directEdgeSource,
253
+ workspaceByName,
254
+ inputDependenciesByName,
255
+ affectedSet,
256
+ ignoreWorkspaceDependencies,
257
+ }) => {
258
+ const chain = [
259
+ {
260
+ workspaceName: startingWorkspaceName,
261
+ },
262
+ {
263
+ workspaceName: directDependencyName,
264
+ edgeSource: directEdgeSource,
265
+ },
266
+ ];
267
+ const visited = new Set([startingWorkspaceName, directDependencyName]);
268
+ let currentName = directDependencyName;
269
+ while (true) {
270
+ const currentWorkspace = workspaceByName.get(currentName);
271
+ if (!currentWorkspace) break;
272
+ const nextEdge = collectDirectEdges({
273
+ workspace: currentWorkspace,
274
+ inputDependenciesByName,
275
+ ignoreWorkspaceDependencies,
276
+ }).find(
277
+ ({ dependencyName }) =>
278
+ !visited.has(dependencyName) &&
279
+ workspaceByName.has(dependencyName) &&
280
+ affectedSet.has(dependencyName),
281
+ );
282
+ if (!nextEdge) break;
283
+ chain.push({
284
+ workspaceName: nextEdge.dependencyName,
285
+ edgeSource: nextEdge.edgeSource,
286
+ });
287
+ visited.add(nextEdge.dependencyName);
288
+ currentName = nextEdge.dependencyName;
289
+ }
290
+ return chain;
291
+ };
207
292
  const collectAffectedDependencies = ({
208
293
  startingWorkspace,
209
294
  workspaceByName,
210
295
  inputDependenciesByName,
211
296
  affectedSet,
212
- ignorePackageDependencies,
297
+ ignoreWorkspaceDependencies,
213
298
  }) => {
214
299
  const results = [];
215
- const visited = new Set([startingWorkspace.name]);
216
- const visit = (currentName, chain) => {
217
- const currentWorkspace = workspaceByName.get(currentName);
218
- if (!currentWorkspace) return;
219
- const edges = [];
220
- for (const dependencyName of inputDependenciesByName.get(currentName) ??
221
- []) {
222
- edges.push({
223
- dependencyName,
224
- edgeSource: "input",
225
- });
226
- }
227
- if (!ignorePackageDependencies) {
228
- for (const dependencyName of currentWorkspace.dependencies) {
229
- edges.push({
230
- dependencyName,
231
- edgeSource: "package",
232
- });
233
- }
234
- }
235
- for (const { dependencyName, edgeSource } of edges) {
236
- if (visited.has(dependencyName)) continue;
237
- if (!workspaceByName.has(dependencyName)) continue;
238
- visited.add(dependencyName);
239
- const dependencyChain = [
240
- ...chain,
241
- {
242
- workspaceName: dependencyName,
243
- edgeSource,
244
- },
245
- ];
246
- if (affectedSet.has(dependencyName)) {
247
- results.push({
248
- dependencyName,
249
- chain: dependencyChain,
250
- });
251
- }
252
- visit(dependencyName, dependencyChain);
253
- }
254
- };
255
- visit(startingWorkspace.name, [
256
- {
257
- workspaceName: startingWorkspace.name,
258
- },
259
- ]);
300
+ const seen = new Set([startingWorkspace.name]);
301
+ const directEdges = collectDirectEdges({
302
+ workspace: startingWorkspace,
303
+ inputDependenciesByName,
304
+ ignoreWorkspaceDependencies,
305
+ });
306
+ for (const { dependencyName, edgeSource } of directEdges) {
307
+ if (seen.has(dependencyName)) continue;
308
+ if (!workspaceByName.has(dependencyName)) continue;
309
+ seen.add(dependencyName);
310
+ if (!affectedSet.has(dependencyName)) continue;
311
+ results.push({
312
+ dependencyName,
313
+ chain: extendChainThroughAffectedDeps({
314
+ startingWorkspaceName: startingWorkspace.name,
315
+ directDependencyName: dependencyName,
316
+ directEdgeSource: edgeSource,
317
+ workspaceByName,
318
+ inputDependenciesByName,
319
+ affectedSet,
320
+ ignoreWorkspaceDependencies,
321
+ }),
322
+ });
323
+ }
260
324
  return results;
261
325
  };
326
+ const filterExternalDepChangesByInputs = ({
327
+ changesByWorkspace,
328
+ workspaceInputs,
329
+ }) => {
330
+ const filtered = new Map();
331
+ for (const { workspace, inputExternalDependencyNames } of workspaceInputs) {
332
+ const changes = changesByWorkspace.get(workspace.name);
333
+ if (!changes?.length) continue;
334
+ if (inputExternalDependencyNames === undefined) {
335
+ filtered.set(workspace.name, changes);
336
+ continue;
337
+ }
338
+ if (inputExternalDependencyNames.length === 0) continue;
339
+ const allowed = new Set(inputExternalDependencyNames);
340
+ const matched = changes.filter((change) => allowed.has(change.name));
341
+ if (matched.length) filtered.set(workspace.name, matched);
342
+ }
343
+ return filtered;
344
+ };
262
345
  const getFileAffectedWorkspaces = async ({
263
346
  rootDirectory,
264
347
  workspaceInputs,
265
348
  changedFilePaths,
266
- ignorePackageDependencies = false,
349
+ externalDepChangesByWorkspace = new Map(),
350
+ ignoreWorkspaceDependencies = false,
267
351
  }) => {
268
352
  const normalizedChangedFilePaths = changedFilePaths.map((filePath) =>
269
353
  normalizeChangedFilePath({
@@ -285,6 +369,10 @@ const getFileAffectedWorkspaces = async ({
285
369
  }),
286
370
  );
287
371
  }
372
+ const filteredExternalDepChanges = filterExternalDepChangesByInputs({
373
+ changesByWorkspace: externalDepChangesByWorkspace,
374
+ workspaceInputs,
375
+ });
288
376
  const inputDependenciesByName = resolveInputWorkspaceDependencies({
289
377
  workspaceInputs,
290
378
  });
@@ -292,17 +380,20 @@ const getFileAffectedWorkspaces = async ({
292
380
  workspaceInputs,
293
381
  workspaceByName,
294
382
  changedFilesByName,
383
+ externalDepChangesByWorkspace: filteredExternalDepChanges,
295
384
  inputDependenciesByName,
296
- ignorePackageDependencies,
385
+ ignoreWorkspaceDependencies,
297
386
  });
298
387
  const affectedWorkspaces = workspaceInputs.map(({ workspace }) => {
299
388
  const changedFiles = changedFilesByName.get(workspace.name) ?? [];
389
+ const externalDependencies =
390
+ filteredExternalDepChanges.get(workspace.name) ?? [];
300
391
  const dependencies = collectAffectedDependencies({
301
392
  startingWorkspace: workspace,
302
393
  workspaceByName,
303
394
  inputDependenciesByName,
304
395
  affectedSet,
305
- ignorePackageDependencies,
396
+ ignoreWorkspaceDependencies,
306
397
  });
307
398
  return {
308
399
  workspace,
@@ -310,6 +401,7 @@ const getFileAffectedWorkspaces = async ({
310
401
  affectedReasons: {
311
402
  changedFiles,
312
403
  dependencies,
404
+ externalDependencies,
313
405
  },
314
406
  };
315
407
  });
@@ -52,6 +52,38 @@ const resolveGitRoot = async (rootDirectory) => {
52
52
  }
53
53
  return result.stdout.trim();
54
54
  };
55
+ /**
56
+ * Read a project-root-relative file's contents at a specific git ref via
57
+ * `git show <ref>:<repo-relative-path>`. Returns `null` if the file does not
58
+ * exist at that ref (e.g. it was added later). Throws on other git errors.
59
+ */ const readProjectFileAtGitRef = async ({
60
+ rootDirectory,
61
+ ref,
62
+ projectRelativePath,
63
+ }) => {
64
+ const gitRoot = fs.realpathSync.native(
65
+ path.resolve(await resolveGitRoot(rootDirectory)),
66
+ );
67
+ const absoluteProjectRoot = fs.realpathSync.native(
68
+ path.resolve(rootDirectory),
69
+ );
70
+ const absoluteFile = path.resolve(absoluteProjectRoot, projectRelativePath);
71
+ const repoRelative = path
72
+ .relative(gitRoot, absoluteFile)
73
+ .split(path.sep)
74
+ .join("/");
75
+ const result = await runGit(["show", `${ref}:${repoRelative}`], gitRoot);
76
+ if (result.exitCode === 0) return result.stdout;
77
+ if (
78
+ result.stderr.includes("does not exist") ||
79
+ result.stderr.includes("exists on disk, but not in")
80
+ ) {
81
+ return null;
82
+ }
83
+ throw new GIT_AFFECTED_ERRORS.GitCommandFailed(
84
+ `git show ${ref}:${repoRelative} failed (exit ${result.exitCode}): ${result.stderr.trim()}`,
85
+ );
86
+ };
55
87
  const toProjectFilePath = ({
56
88
  gitRoot,
57
89
  absoluteProjectRoot,
@@ -83,6 +115,10 @@ const getGitAffectedFiles = async (options) => {
83
115
  const includeStaged = !ignoreUncommitted && !ignoreStaged;
84
116
  const includeUnstaged = !ignoreUncommitted && !ignoreUnstaged;
85
117
  const includeUntracked = !ignoreUncommitted && !ignoreUntracked;
118
+ const [baseSha, headSha] = await Promise.all([
119
+ runGitOrThrow(["rev-parse", baseRef], gitRoot).then((out) => out.trim()),
120
+ runGitOrThrow(["rev-parse", headRef], gitRoot).then((out) => out.trim()),
121
+ ]);
86
122
  const collectors = [
87
123
  runGitOrThrow(
88
124
  ["diff", "--name-only", "-z", baseRef, headRef],
@@ -147,7 +183,14 @@ const getGitAffectedFiles = async (options) => {
147
183
  .sort((a, b) => a.projectFilePath.localeCompare(b.projectFilePath));
148
184
  return {
149
185
  files,
186
+ baseSha,
187
+ headSha,
150
188
  };
151
189
  };
152
190
 
153
- export { GIT_AFFECTED_ERRORS, GIT_AFFECTED_FILE_REASONS, getGitAffectedFiles };
191
+ export {
192
+ GIT_AFFECTED_ERRORS,
193
+ GIT_AFFECTED_FILE_REASONS,
194
+ getGitAffectedFiles,
195
+ readProjectFileAtGitRef,
196
+ };
@@ -1,22 +1,90 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { parseBunLockPackageVersions } from "../internal/bun/bunLock.mjs";
4
+ import { BunWorkspacesError } from "../internal/core/index.mjs";
5
+ import { logger } from "../internal/logger/index.mjs";
6
+ import { computeExternalDependencyChanges } from "./externalDependencyChanges.mjs";
1
7
  import { getFileAffectedWorkspaces } from "./fileAffectedWorkspaces.mjs";
2
- import { getGitAffectedFiles } from "./gitAffectedFiles.mjs";
8
+ import {
9
+ getGitAffectedFiles,
10
+ readProjectFileAtGitRef,
11
+ } from "./gitAffectedFiles.mjs";
3
12
 
13
+ const BUN_LOCK_PROJECT_RELATIVE_PATH = "bun.lock";
14
+ const readCurrentBunLock = (rootDirectory) => {
15
+ const lockPath = path.join(rootDirectory, BUN_LOCK_PROJECT_RELATIVE_PATH);
16
+ try {
17
+ return fs.readFileSync(lockPath, "utf8");
18
+ } catch {
19
+ return null;
20
+ }
21
+ };
22
+ const loadVersionsAt = async (rootDirectory, ref) => {
23
+ const contents = await readProjectFileAtGitRef({
24
+ rootDirectory,
25
+ ref,
26
+ projectRelativePath: BUN_LOCK_PROJECT_RELATIVE_PATH,
27
+ });
28
+ if (contents === null) return new Map();
29
+ const parsed = parseBunLockPackageVersions(contents);
30
+ if (parsed instanceof BunWorkspacesError) {
31
+ logger.warn(
32
+ `Could not parse bun.lock at ref "${ref}": ${parsed.message}. Treating as empty.`,
33
+ );
34
+ return new Map();
35
+ }
36
+ return parsed;
37
+ };
38
+ const loadCurrentVersions = (rootDirectory) => {
39
+ const contents = readCurrentBunLock(rootDirectory);
40
+ if (contents === null) return new Map();
41
+ const parsed = parseBunLockPackageVersions(contents);
42
+ if (parsed instanceof BunWorkspacesError) {
43
+ logger.warn(
44
+ `Could not parse current bun.lock: ${parsed.message}. Treating as empty.`,
45
+ );
46
+ return new Map();
47
+ }
48
+ return parsed;
49
+ };
4
50
  const getGitAffectedWorkspaces = async ({
5
51
  rootDirectory,
6
52
  workspacesOptions,
7
53
  gitOptions,
8
54
  }) => {
9
- const { files: gitFiles } = await getGitAffectedFiles({
55
+ const {
56
+ files: gitFiles,
57
+ baseSha,
58
+ headSha,
59
+ } = await getGitAffectedFiles({
10
60
  rootDirectory,
11
61
  ...gitOptions,
12
62
  });
13
63
  const gitFileByPath = new Map(
14
64
  gitFiles.map((file) => [file.projectFilePath, file]),
15
65
  );
66
+ const projectWorkspaces = workspacesOptions.workspaces ?? [];
67
+ const externalDepChangesByWorkspace =
68
+ workspacesOptions.ignoreExternalDependencies || !projectWorkspaces.length
69
+ ? new Map()
70
+ : computeExternalDependencyChanges({
71
+ workspaces: projectWorkspaces,
72
+ baseLock: await loadVersionsAt(rootDirectory, gitOptions.baseRef),
73
+ headLock:
74
+ gitOptions.headRef === "HEAD"
75
+ ? loadCurrentVersions(rootDirectory)
76
+ : await loadVersionsAt(rootDirectory, gitOptions.headRef),
77
+ });
78
+ const {
79
+ workspaces: _omit,
80
+ ignoreExternalDependencies: _omit2,
81
+ ...fileOpts
82
+ } = workspacesOptions;
16
83
  const { affectedWorkspaces } = await getFileAffectedWorkspaces({
17
84
  rootDirectory,
18
- ...workspacesOptions,
85
+ ...fileOpts,
19
86
  changedFilePaths: gitFiles.map((file) => file.projectFilePath),
87
+ externalDepChangesByWorkspace,
20
88
  });
21
89
  const annotatedWorkspaces = affectedWorkspaces.map((result) => ({
22
90
  ...result,
@@ -32,6 +100,8 @@ const getGitAffectedWorkspaces = async ({
32
100
  }));
33
101
  return {
34
102
  affectedWorkspaces: annotatedWorkspaces,
103
+ baseSha,
104
+ headSha,
35
105
  };
36
106
  };
37
107
 
@@ -1,5 +1,7 @@
1
1
  export * from "./fileAffectedWorkspaces.mjs";
2
2
  export * from "./gitAffectedFiles.mjs";
3
3
  export * from "./gitAffectedWorkspaces.mjs";
4
+ export * from "./affectedBaseRef.mjs";
5
+ export * from "./externalDependencyChanges.mjs";
4
6
 
5
7
  export {};
@@ -1,4 +1,4 @@
1
- import { createFileSystemProject } from "../../project/implementations/fileSystemProject.mjs";
1
+ import { createFileSystemProject } from "../../project/implementations/fileSystemProject/index.mjs";
2
2
 
3
3
  const SERVER_STATE = {
4
4
  workingDirectory: null,
@@ -3,12 +3,17 @@ import { BunWorkspacesError } from "../../internal/core/error/index.mjs";
3
3
  import { createLogger, logger } from "../../internal/logger/index.mjs";
4
4
  import { getCliCommandConfig } from "../../2392.mjs";
5
5
 
6
- /** Splits workspace patterns by whitespace, but allows escaping spaces via backslash */ const splitWorkspacePatterns =
7
- (workspacePatterns) =>
8
- workspacePatterns
9
- .split(/(?<!\\)\s+/)
10
- .filter(Boolean)
11
- .map((pattern) => pattern.replace(/\\\s/g, " "));
6
+ /**
7
+ * Splits a multi-value CLI arg on whitespace (any of space/tab/newline). A
8
+ * literal space inside a value can be preserved by escaping it with a
9
+ * backslash (e.g. `path/with\ space`). Used for `--files` and
10
+ * `--workspace-patterns`, both of which accept output of `$(bw ...)`
11
+ * substitutions, which are typically newline-separated.
12
+ */ const splitWhitespaceArg = (raw) =>
13
+ raw
14
+ .split(/(?<!\\)\s+/)
15
+ .filter(Boolean)
16
+ .map((value) => value.replace(/\\\s/g, " "));
12
17
  const createWorkspaceInfoLines = (workspace) => [
13
18
  `Workspace: ${workspace.name}${workspace.isRoot ? " (root)" : ""}`,
14
19
  ` - Aliases: ${workspace.aliases.join(", ")}`,
@@ -85,5 +90,5 @@ export {
85
90
  createWorkspaceInfoLines,
86
91
  handleGlobalCommand,
87
92
  handleProjectCommand,
88
- splitWorkspacePatterns,
93
+ splitWhitespaceArg,
89
94
  };
@@ -7,8 +7,9 @@ import {
7
7
  tagInfo,
8
8
  workspaceInfo,
9
9
  } from "./handleSimpleCommands.mjs";
10
+ import { listAffected } from "./listAffected.mjs";
10
11
  import { mcpServer } from "./mcp.mjs";
11
- import { runScript } from "./runScript/index.mjs";
12
+ import { runAffected, runScript } from "./runScript/index.mjs";
12
13
 
13
14
  const defineGlobalCommands = (context) => {
14
15
  doctor(context);
@@ -22,6 +23,8 @@ const defineProjectCommands = (context) => {
22
23
  tagInfo(context);
23
24
  mcpServer(context);
24
25
  runScript(context);
26
+ listAffected(context);
27
+ runAffected(context);
25
28
  };
26
29
 
27
30
  export { defineGlobalCommands, defineProjectCommands };
@@ -7,7 +7,7 @@ import {
7
7
  createWorkspaceInfoLines,
8
8
  handleGlobalCommand,
9
9
  handleProjectCommand,
10
- splitWorkspacePatterns,
10
+ splitWhitespaceArg,
11
11
  } from "./commandHandlerUtils.mjs";
12
12
  import { isJSONObject } from "../../8257.mjs";
13
13
 
@@ -51,7 +51,7 @@ const listWorkspaces = handleProjectCommand(
51
51
  }
52
52
  const patterns = positionalWorkspacePatterns?.length
53
53
  ? positionalWorkspacePatterns
54
- : splitWorkspacePatterns(options.workspacePatterns ?? "");
54
+ : splitWhitespaceArg(options.workspacePatterns ?? "");
55
55
  const workspaces = patterns?.length
56
56
  ? project.findWorkspacesByPattern(...patterns)
57
57
  : project.workspaces;