bun-workspaces 1.8.2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +537 -0
- package/README.md +51 -13
- package/package.json +1 -1
- package/src/2392.mjs +184 -3
- package/src/5166.mjs +1 -0
- package/src/8529.mjs +10 -0
- package/src/affected/affectedBaseRef.mjs +12 -0
- package/src/affected/externalDependencyChanges.mjs +47 -0
- package/src/affected/fileAffectedWorkspaces.mjs +152 -54
- package/src/affected/gitAffectedFiles.mjs +44 -1
- package/src/affected/gitAffectedWorkspaces.mjs +73 -3
- package/src/affected/index.mjs +2 -0
- package/src/ai/mcp/serverState.mjs +1 -1
- package/src/cli/commands/commandHandlerUtils.mjs +12 -7
- package/src/cli/commands/commands.mjs +4 -1
- package/src/cli/commands/handleSimpleCommands.mjs +2 -2
- package/src/cli/commands/listAffected.mjs +184 -0
- package/src/cli/commands/runScript/handleRunAffected.mjs +99 -0
- package/src/cli/commands/runScript/handleRunScript.mjs +19 -202
- package/src/cli/commands/runScript/index.mjs +1 -0
- package/src/cli/commands/runScript/scriptRunFlow.mjs +213 -0
- package/src/cli/index.d.ts +749 -134
- package/src/config/public.d.ts +66 -2
- package/src/config/rootConfig/rootConfig.mjs +9 -9
- package/src/config/rootConfig/rootConfigSchema.mjs +3 -0
- package/src/config/workspaceConfig/mergeWorkspaceConfig.mjs +33 -19
- package/src/config/workspaceConfig/workspaceConfig.mjs +3 -0
- package/src/config/workspaceConfig/workspaceConfigSchema.mjs +26 -0
- package/src/index.d.ts +307 -5
- package/src/index.mjs +1 -0
- package/src/internal/bun/bunLock.mjs +33 -0
- package/src/internal/generated/aiDocs/docs.mjs +169 -9
- package/src/internal/generated/ajv/validateRootConfig.mjs +1 -1
- package/src/internal/generated/ajv/validateWorkspaceConfig.mjs +1 -1
- package/src/project/implementations/fileSystemProject/affectedWorkspaces.mjs +227 -0
- package/src/project/implementations/{fileSystemProject.mjs → fileSystemProject/fileSystemProject.mjs} +169 -12
- package/src/project/implementations/fileSystemProject/index.mjs +4 -0
- package/src/project/implementations/memoryProject.mjs +1 -0
- package/src/project/implementations/projectBase.mjs +11 -17
- package/src/project/index.mjs +1 -1
- package/src/rslib-runtime.mjs +0 -31
- package/src/workspaces/applyWorkspacePatternConfigs.mjs +16 -2
- package/src/workspaces/dependencyGraph/resolveDependencies.mjs +68 -18
- package/src/workspaces/dependencyGraph/validateDependencyRules.mjs +14 -7
- package/src/workspaces/findWorkspaces.mjs +3 -0
- package/src/workspaces/workspace.mjs +8 -2
- package/src/workspaces/workspacePattern.mjs +134 -46
|
@@ -17,7 +17,7 @@ const getTransitiveDeps = (workspaceName, workspaceMap, chain, visited) => {
|
|
|
17
17
|
}
|
|
18
18
|
return result;
|
|
19
19
|
};
|
|
20
|
-
const validateWorkspaceDependencyRules = ({ workspaceMap }) => {
|
|
20
|
+
const validateWorkspaceDependencyRules = ({ workspaceMap, rootWorkspace }) => {
|
|
21
21
|
const violations = [];
|
|
22
22
|
for (const [workspaceName, { config }] of Object.entries(workspaceMap)) {
|
|
23
23
|
const rule = config.rules?.workspaceDependencies;
|
|
@@ -32,10 +32,15 @@ const validateWorkspaceDependencyRules = ({ workspaceMap }) => {
|
|
|
32
32
|
const depWorkspace = workspaceMap[depName]?.workspace;
|
|
33
33
|
if (!depWorkspace) continue;
|
|
34
34
|
const chainStr = chain.join(" -> ");
|
|
35
|
+
// matchWorkspacesByPatterns can inject the root workspace when an
|
|
36
|
+
// "@root" pattern is present, even if it isn't in the input universe.
|
|
37
|
+
// We're only asking "does the single dep match?" so confirm by name.
|
|
35
38
|
if (rule.allowPatterns) {
|
|
36
|
-
const isAllowed =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
const isAllowed = matchWorkspacesByPatterns(
|
|
40
|
+
rule.allowPatterns,
|
|
41
|
+
[depWorkspace],
|
|
42
|
+
rootWorkspace,
|
|
43
|
+
).some((matched) => matched.name === depWorkspace.name);
|
|
39
44
|
if (!isAllowed) {
|
|
40
45
|
violations.push(
|
|
41
46
|
`"${workspaceName}" violates workspaceDependencies rule: workspace "${depName}" is not permitted by allowPatterns (dependency chain: ${chainStr})`,
|
|
@@ -44,9 +49,11 @@ const validateWorkspaceDependencyRules = ({ workspaceMap }) => {
|
|
|
44
49
|
}
|
|
45
50
|
}
|
|
46
51
|
if (rule.denyPatterns) {
|
|
47
|
-
const isDenied =
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
const isDenied = matchWorkspacesByPatterns(
|
|
53
|
+
rule.denyPatterns,
|
|
54
|
+
[depWorkspace],
|
|
55
|
+
rootWorkspace,
|
|
56
|
+
).some((matched) => matched.name === depWorkspace.name);
|
|
50
57
|
if (isDenied) {
|
|
51
58
|
violations.push(
|
|
52
59
|
`"${workspaceName}" violates workspaceDependencies rule: workspace "${depName}" is denied by denyPatterns (dependency chain: ${chainStr})`,
|
|
@@ -144,6 +144,7 @@ const findWorkspaces = ({
|
|
|
144
144
|
tags: workspaceConfig?.tags ?? [],
|
|
145
145
|
dependencies: [],
|
|
146
146
|
dependents: [],
|
|
147
|
+
externalDependencies: [],
|
|
147
148
|
};
|
|
148
149
|
if (workspace.isRoot) {
|
|
149
150
|
logger.debug(`Found root workspace: ${workspace.name}`);
|
|
@@ -177,10 +178,12 @@ const findWorkspaces = ({
|
|
|
177
178
|
workspaceMap,
|
|
178
179
|
workspaceAliases,
|
|
179
180
|
workspacePatternConfigs,
|
|
181
|
+
rootWorkspace,
|
|
180
182
|
);
|
|
181
183
|
}
|
|
182
184
|
validateWorkspaceDependencyRules({
|
|
183
185
|
workspaceMap,
|
|
186
|
+
rootWorkspace,
|
|
184
187
|
});
|
|
185
188
|
validateWorkspaceAliases(workspaces, workspaceAliases, rootWorkspace.name);
|
|
186
189
|
logger.debug(
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Catalog reference info attached to an `ExternalDependency` that uses a `catalog:` ref */ /** The four `package.json` dependency maps a dep can be declared in */ const EXTERNAL_DEPENDENCY_SOURCES =
|
|
2
|
+
[
|
|
3
|
+
"dependencies",
|
|
4
|
+
"devDependencies",
|
|
5
|
+
"peerDependencies",
|
|
6
|
+
"optionalDependencies",
|
|
7
|
+
];
|
|
2
8
|
|
|
3
|
-
export {};
|
|
9
|
+
export { EXTERNAL_DEPENDENCY_SOURCES };
|
|
@@ -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
|
|
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
|
-
|
|
60
|
+
afterNegation.startsWith(target + WORKSPACE_PATTERN_SEPARATOR),
|
|
25
61
|
);
|
|
26
62
|
if (!target) {
|
|
27
63
|
return {
|
|
28
64
|
target: "default",
|
|
29
|
-
value:
|
|
65
|
+
value: afterNegation,
|
|
30
66
|
isNegated,
|
|
67
|
+
isRegex: false,
|
|
68
|
+
isRootSelector: false,
|
|
31
69
|
};
|
|
32
70
|
}
|
|
33
|
-
const
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
};
|