bun-workspaces 1.9.0 → 1.11.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 (40) hide show
  1. package/AGENTS.md +537 -0
  2. package/README.md +4 -2
  3. package/package.json +1 -1
  4. package/src/2392.mjs +10 -1
  5. package/src/5166.mjs +1 -0
  6. package/src/affected/fileAffectedWorkspaces.mjs +7 -1
  7. package/src/affected/gitAffectedFiles.mjs +26 -1
  8. package/src/ai/mcp/bwMcpServer.mjs +5 -1
  9. package/src/ai/mcp/serverState.mjs +10 -1
  10. package/src/ai/mcp/tools.mjs +1 -1
  11. package/src/cli/commands/commandHandlerUtils.mjs +11 -10
  12. package/src/cli/commands/commands.mjs +1 -1
  13. package/src/cli/commands/handleSimpleCommands.mjs +7 -6
  14. package/src/cli/commands/listAffected.mjs +17 -13
  15. package/src/cli/commands/mcp.mjs +11 -1
  16. package/src/cli/commands/runScript/output/renderGroupedOutput.mjs +3 -2
  17. package/src/cli/commands/runScript/output/renderPlainOutput.mjs +4 -1
  18. package/src/cli/commands/runScript/scriptRunFlow.mjs +8 -3
  19. package/src/cli/createCli.mjs +8 -2
  20. package/src/cli/globalOptions/globalOptions.mjs +5 -4
  21. package/src/cli/index.d.ts +35 -2
  22. package/src/config/rootConfig/loadRootConfig.mjs +2 -1
  23. package/src/config/rootConfig/rootConfig.mjs +5 -9
  24. package/src/config/userEnvVars/userEnvVars.mjs +12 -1
  25. package/src/config/util/loadConfig.mjs +23 -2
  26. package/src/config/workspaceConfig/loadWorkspaceConfig.mjs +2 -1
  27. package/src/index.d.ts +11 -0
  28. package/src/internal/core/language/string/index.mjs +1 -0
  29. package/src/internal/core/language/string/sanitizeOutput.mjs +15 -0
  30. package/src/internal/core/runtime/tempFile.mjs +20 -2
  31. package/src/internal/generated/aiDocs/docs.mjs +19 -8
  32. package/src/project/implementations/fileSystemProject/affectedWorkspaces.mjs +2 -0
  33. package/src/project/implementations/fileSystemProject/fileSystemProject.mjs +39 -14
  34. package/src/project/implementations/projectBase.mjs +11 -17
  35. package/src/runScript/scriptExecution.mjs +1 -1
  36. package/src/runScript/workspaceScriptMetadata.mjs +24 -1
  37. package/src/workspaces/applyWorkspacePatternConfigs.mjs +6 -1
  38. package/src/workspaces/dependencyGraph/validateDependencyRules.mjs +14 -7
  39. package/src/workspaces/findWorkspaces.mjs +4 -0
  40. package/src/workspaces/workspacePattern.mjs +134 -46
@@ -12,95 +12,183 @@ const WORKSPACE_PATTERN_NEGATION_PREFIXES = [
12
12
  WORKSPACE_PATTERN_NEGATION_SHORT_PREFIX,
13
13
  ];
14
14
  const WORKSPACE_PATTERN_SEPARATOR = ":";
15
+ const WORKSPACE_PATTERN_REGEX_PREFIX = "re:";
16
+ const validateRegexSource = (source, originalPattern) => {
17
+ try {
18
+ new RegExp(source);
19
+ } catch (cause) {
20
+ throw new WORKSPACE_PATTERN_ERRORS.InvalidWorkspacePattern(
21
+ `Invalid regex in workspace pattern "${originalPattern}": ${cause.message}`,
22
+ );
23
+ }
24
+ };
15
25
  const parseWorkspacePattern = (pattern) => {
16
26
  const negationPrefix = WORKSPACE_PATTERN_NEGATION_PREFIXES.find((prefix) =>
17
27
  pattern.startsWith(prefix),
18
28
  );
19
29
  const isNegated = !!negationPrefix;
20
- const patternValue = negationPrefix
30
+ const afterNegation = negationPrefix
21
31
  ? pattern.slice(negationPrefix.length)
22
32
  : pattern;
33
+ // The "@root" selector resolves to the project's root workspace. Recognized
34
+ // immediately after optional negation, so "not:@root" / "!@root" also work.
35
+ // A target-scoped value of "@root" (e.g. "name:@root") is treated as a literal,
36
+ // not a root selector.
37
+ if (afterNegation === /* inlined export .ROOT_WORKSPACE_SELECTOR */ "@root") {
38
+ return {
39
+ target: "default",
40
+ value: /* inlined export .ROOT_WORKSPACE_SELECTOR */ "@root",
41
+ isNegated,
42
+ isRegex: false,
43
+ isRootSelector: true,
44
+ };
45
+ }
46
+ // "re:" before any target consumes the rest as a regex against the default target.
47
+ // e.g. "re:path:foo" → default-target regex over literal source "path:foo".
48
+ if (afterNegation.startsWith(WORKSPACE_PATTERN_REGEX_PREFIX)) {
49
+ const value = afterNegation.slice(WORKSPACE_PATTERN_REGEX_PREFIX.length);
50
+ validateRegexSource(value, pattern);
51
+ return {
52
+ target: "default",
53
+ value,
54
+ isNegated,
55
+ isRegex: true,
56
+ isRootSelector: false,
57
+ };
58
+ }
23
59
  const target = TARGETS.find((target) =>
24
- patternValue.startsWith(target + WORKSPACE_PATTERN_SEPARATOR),
60
+ afterNegation.startsWith(target + WORKSPACE_PATTERN_SEPARATOR),
25
61
  );
26
62
  if (!target) {
27
63
  return {
28
64
  target: "default",
29
- value: patternValue,
65
+ value: afterNegation,
30
66
  isNegated,
67
+ isRegex: false,
68
+ isRootSelector: false,
31
69
  };
32
70
  }
33
- const value = patternValue.slice(
71
+ const afterTarget = afterNegation.slice(
34
72
  target.length + WORKSPACE_PATTERN_SEPARATOR.length,
35
73
  );
74
+ if (afterTarget.startsWith(WORKSPACE_PATTERN_REGEX_PREFIX)) {
75
+ const value = afterTarget.slice(WORKSPACE_PATTERN_REGEX_PREFIX.length);
76
+ validateRegexSource(value, pattern);
77
+ return {
78
+ target,
79
+ value,
80
+ isNegated,
81
+ isRegex: true,
82
+ isRootSelector: false,
83
+ };
84
+ }
36
85
  return {
37
86
  target,
38
- value,
87
+ value: afterTarget,
39
88
  isNegated,
89
+ isRegex: false,
90
+ isRootSelector: false,
40
91
  };
41
92
  };
42
- const stringifyWorkspacePattern = (pattern) => {
43
- return `${pattern.target}${WORKSPACE_PATTERN_SEPARATOR}${pattern.value}`;
44
- };
45
93
  const PATTERN_TARGET_HANDLERS = {
46
- default: (pattern, workspaces, wildcardRegex) => {
47
- return workspaces.filter((workspace) => {
48
- return (
49
- (pattern.value.includes("*")
50
- ? wildcardRegex.test(workspace.name)
51
- : workspace.name === pattern.value) ||
52
- workspace.aliases.some((alias) =>
53
- pattern.value.includes("*")
54
- ? wildcardRegex.test(alias)
55
- : alias === pattern.value,
56
- )
94
+ default: (pattern, workspaces) => {
95
+ // Plain string at the default target matches name OR alias. Wildcard and
96
+ // regex forms intentionally narrow to name only to avoid ambiguity — use
97
+ // an explicit "alias:" prefix to match aliases by wildcard/regex.
98
+ if (pattern.isRegex) {
99
+ const regex = new RegExp(pattern.value);
100
+ return workspaces.filter((workspace) => regex.test(workspace.name));
101
+ }
102
+ if (pattern.value.includes("*")) {
103
+ const wildcardRegex = createWildcardRegex(pattern.value);
104
+ return workspaces.filter((workspace) =>
105
+ wildcardRegex.test(workspace.name),
57
106
  );
58
- });
107
+ }
108
+ return workspaces.filter(
109
+ (workspace) =>
110
+ workspace.name === pattern.value ||
111
+ workspace.aliases.includes(pattern.value),
112
+ );
59
113
  },
60
- name: (pattern, workspaces, wildcardRegex) => {
61
- return workspaces.filter((workspace) => {
62
- return pattern.value.includes("*")
63
- ? wildcardRegex.test(workspace.name)
64
- : workspace.name === pattern.value;
65
- });
114
+ name: (pattern, workspaces) => {
115
+ if (pattern.isRegex) {
116
+ const regex = new RegExp(pattern.value);
117
+ return workspaces.filter((workspace) => regex.test(workspace.name));
118
+ }
119
+ if (pattern.value.includes("*")) {
120
+ const wildcardRegex = createWildcardRegex(pattern.value);
121
+ return workspaces.filter((workspace) =>
122
+ wildcardRegex.test(workspace.name),
123
+ );
124
+ }
125
+ return workspaces.filter((workspace) => workspace.name === pattern.value);
66
126
  },
67
- alias: (pattern, workspaces, wildcardRegex) => {
68
- return workspaces.filter((workspace) => {
69
- return pattern.value.includes("*")
70
- ? workspace.aliases.some((alias) => wildcardRegex.test(alias))
71
- : workspace.aliases.includes(pattern.value);
72
- });
127
+ alias: (pattern, workspaces) => {
128
+ if (pattern.isRegex) {
129
+ const regex = new RegExp(pattern.value);
130
+ return workspaces.filter((workspace) =>
131
+ workspace.aliases.some((alias) => regex.test(alias)),
132
+ );
133
+ }
134
+ if (pattern.value.includes("*")) {
135
+ const wildcardRegex = createWildcardRegex(pattern.value);
136
+ return workspaces.filter((workspace) =>
137
+ workspace.aliases.some((alias) => wildcardRegex.test(alias)),
138
+ );
139
+ }
140
+ return workspaces.filter((workspace) =>
141
+ workspace.aliases.includes(pattern.value),
142
+ );
73
143
  },
74
144
  path: (pattern, workspaces) => {
145
+ if (pattern.isRegex) {
146
+ const regex = new RegExp(pattern.value);
147
+ // Normalize backslashes so regex sources stay portable: a single
148
+ // forward-slash-based regex works on both Windows and POSIX paths.
149
+ return workspaces.filter((workspace) =>
150
+ regex.test(workspace.path.replaceAll("\\", "/")),
151
+ );
152
+ }
75
153
  return workspaces.filter((workspace) =>
76
154
  new bun.Glob(pattern.value.replace(/\/+$/, "")).match(workspace.path),
77
155
  );
78
156
  },
79
- tag: (pattern, workspaces, wildcardRegex) => {
157
+ tag: (pattern, workspaces) => {
158
+ if (pattern.isRegex) {
159
+ const regex = new RegExp(pattern.value);
160
+ return workspaces.filter((workspace) =>
161
+ workspace.tags.some((tag) => regex.test(tag)),
162
+ );
163
+ }
164
+ if (pattern.value.includes("*")) {
165
+ const wildcardRegex = createWildcardRegex(pattern.value);
166
+ return workspaces.filter((workspace) =>
167
+ workspace.tags.some((tag) => wildcardRegex.test(tag)),
168
+ );
169
+ }
80
170
  return workspaces.filter((workspace) =>
81
- pattern.value.includes("*")
82
- ? workspace.tags.some((tag) => wildcardRegex.test(tag))
83
- : workspace.tags.includes(pattern.value),
171
+ workspace.tags.includes(pattern.value),
84
172
  );
85
173
  },
86
174
  };
87
- const matchWorkspacesByPattern = (pattern, workspaces) =>
88
- PATTERN_TARGET_HANDLERS[pattern.target](
89
- pattern,
90
- workspaces,
91
- createWildcardRegex(pattern.value),
92
- );
93
- const matchWorkspacesByPatterns = (patterns, workspaces) => {
175
+ const matchWorkspacesByPattern = (pattern, workspaces, rootWorkspace) => {
176
+ if (pattern.isRootSelector) {
177
+ return rootWorkspace ? [rootWorkspace] : [];
178
+ }
179
+ return PATTERN_TARGET_HANDLERS[pattern.target](pattern, workspaces);
180
+ };
181
+ const matchWorkspacesByPatterns = (patterns, workspaces, rootWorkspace) => {
94
182
  const parsedPatterns = patterns.map(parseWorkspacePattern);
95
183
  const excludePatterns = parsedPatterns.filter((pattern) => pattern.isNegated);
96
184
  const includePatterns = parsedPatterns.filter(
97
185
  (pattern) => !pattern.isNegated,
98
186
  );
99
187
  const excludeWorkspaces = excludePatterns.flatMap((pattern) =>
100
- matchWorkspacesByPattern(pattern, workspaces),
188
+ matchWorkspacesByPattern(pattern, workspaces, rootWorkspace),
101
189
  );
102
190
  const includeWorkspaces = includePatterns.flatMap((pattern) =>
103
- matchWorkspacesByPattern(pattern, workspaces),
191
+ matchWorkspacesByPattern(pattern, workspaces, rootWorkspace),
104
192
  );
105
193
  return includeWorkspaces.filter(
106
194
  (workspace, index, arr) =>
@@ -118,8 +206,8 @@ export {
118
206
  WORKSPACE_PATTERN_NEGATION_PREFIX,
119
207
  WORKSPACE_PATTERN_NEGATION_PREFIXES,
120
208
  WORKSPACE_PATTERN_NEGATION_SHORT_PREFIX,
209
+ WORKSPACE_PATTERN_REGEX_PREFIX,
121
210
  WORKSPACE_PATTERN_SEPARATOR,
122
211
  matchWorkspacesByPatterns,
123
212
  parseWorkspacePattern,
124
- stringifyWorkspacePattern,
125
213
  };