Skip to content

Commit 7385506

Browse files
fix: wildcard pattern matching for root-level and filename patterns
1 parent 78fd050 commit 7385506

File tree

3 files changed

+1119
-441
lines changed

3 files changed

+1119
-441
lines changed

src/utils/get-build-entry-points/expand-exports-wildcards.ts

Lines changed: 118 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,35 +7,50 @@ const extensionPattern = /\.[^./]+$/;
77

88
/**
99
* Extract wildcard match from file path based on pattern
10-
* @param filePath - File path with extension
10+
* @param pathWithoutExtension - File path without extension
1111
* @param patternParts - Parts between wildcards (e.g., [""] or ["/index", ""])
1212
* @returns Captured wildcard value, or undefined if no match
1313
*/
1414
const extractWildcardMatch = (
15-
filePath: string,
15+
pathWithoutExtension: string,
1616
patternParts: string[],
1717
): string | undefined => {
18-
const pathWithoutExtension = filePath.replace(extensionPattern, '');
19-
2018
// Early validation: check if path ends with the last pattern part
2119
const lastPart = patternParts.at(-1)!;
2220
if (!pathWithoutExtension.endsWith(lastPart)) {
2321
return;
2422
}
2523

26-
// Multiple wildcards: find the repeated capture value by trying different capture lengths
27-
// Example: "./*" → "./dist/*/*/index.js" with "a/b/a/b/index" captures "a/b"
28-
// Example: "./*" → "./dist/*/_/*/_/*.mjs" with "foo/_/foo/_/foo" captures "foo"
29-
const segments = pathWithoutExtension.split('/').filter(Boolean);
24+
// Detect if this is a path-based pattern (contains '/') or filename-based pattern
25+
const isPathPattern = patternParts.some(part => part.includes('/'));
26+
27+
if (isPathPattern) {
28+
// Path-based patterns: split by '/' and try different segment lengths
29+
// Example: "./*" → "./dist/*/*/index.js" with "a/b/a/b/index" captures "a/b"
30+
// Example: "./*" → "./dist/*/_/*/_/*.mjs" with "foo/_/foo/_/foo" captures "foo"
31+
const segments = pathWithoutExtension.split('/').filter(Boolean);
3032

31-
// Try different capture lengths (from 1 segment up to total length)
32-
for (let captureLength = 1; captureLength <= segments.length; captureLength += 1) {
33-
const captureValue = segments.slice(0, captureLength).join('/');
33+
// Try different capture lengths (from 1 segment up to total length)
34+
for (let captureLength = 1; captureLength <= segments.length; captureLength += 1) {
35+
const captureValue = segments.slice(0, captureLength).join('/');
3436

35-
// Build expected pattern by joining parts with the capture value
36-
const expectedPath = captureValue + patternParts.join(captureValue);
37-
if (expectedPath === pathWithoutExtension) {
38-
return expectedPath;
37+
// Build expected pattern by joining parts with the capture value
38+
const expectedPath = captureValue + patternParts.join(captureValue);
39+
if (expectedPath === pathWithoutExtension) {
40+
return expectedPath;
41+
}
42+
}
43+
} else {
44+
// Filename-based patterns: try different character lengths
45+
// Example: "x.*.y.*" with "foo.y.foo" should capture "foo"
46+
for (let captureLength = 1; captureLength <= pathWithoutExtension.length; captureLength += 1) {
47+
const captureValue = pathWithoutExtension.slice(0, captureLength);
48+
49+
// Build expected pattern by joining parts with the capture value
50+
const expectedPath = captureValue + patternParts.join(captureValue);
51+
if (expectedPath === pathWithoutExtension) {
52+
return expectedPath;
53+
}
3954
}
4055
}
4156
};
@@ -80,9 +95,77 @@ export const expandBuildOutputWildcards = async (
8095
// Split by wildcards to get prefix and pattern parts
8196
const [outputPrefix, ...patternParts] = outputPathWithoutExtension.split('*');
8297

83-
// Find the matching src:dist pair (most specific first)
98+
// Separate directory path from filename prefix
99+
// Find the last '/' to split directory from filename component
100+
const lastSlashIndex = outputPrefix.lastIndexOf('/');
101+
const directoryPrefix = lastSlashIndex === -1
102+
? outputPrefix
103+
: outputPrefix.slice(0, lastSlashIndex + 1);
104+
const filenamePrefix = lastSlashIndex >= 0
105+
? outputPrefix.slice(lastSlashIndex + 1)
106+
: '';
107+
108+
// Helper function to check if a file matches the wildcard pattern
109+
const matchesPattern = (filePath: string): boolean => {
110+
// Strip source extension for pattern matching
111+
const filePathWithoutExtension = filePath.replace(extensionPattern, '');
112+
const fileName = filePathWithoutExtension.split('/').at(-1) || '';
113+
114+
// Check filename prefix
115+
if (filenamePrefix && !fileName.startsWith(filenamePrefix)) {
116+
return false;
117+
}
118+
119+
// For single wildcard patterns, check suffix
120+
if (patternParts.length === 1) {
121+
const suffix = patternParts[0];
122+
if (suffix) {
123+
// If suffix contains path separators, check against full path
124+
// Otherwise, check against filename only
125+
const checkTarget = suffix.includes('/') ? filePathWithoutExtension : fileName;
126+
if (!checkTarget.endsWith(suffix)) {
127+
return false;
128+
}
129+
}
130+
return true;
131+
}
132+
133+
// For multiple wildcard patterns, use extractWildcardMatch
134+
// This validates that all wildcards capture the same value
135+
// For filename patterns with prefix, strip it before matching
136+
const matchTarget = filenamePrefix
137+
? fileName.slice(filenamePrefix.length)
138+
: filePathWithoutExtension;
139+
return !!extractWildcardMatch(matchTarget, patternParts);
140+
};
141+
142+
// Handle root-level patterns (directoryPrefix is './')
143+
if (directoryPrefix === './') {
144+
// Expand for each srcdist pair
145+
const allExpansions = await Promise.all(
146+
sortedByDistLength.map(async (srcdist) => {
147+
const srcPath = srcdist.srcResolved;
148+
const allFiles = await getDirectoryFiles(srcPath);
149+
return allFiles.flatMap((filePath) => {
150+
if (!matchesPattern(filePath)) {
151+
return [];
152+
}
153+
154+
// Strip source extension before appending output extension
155+
const filePathWithoutExtension = filePath.replace(extensionPattern, '');
156+
return [{
157+
...output,
158+
outputPath: srcdist.dist + filePathWithoutExtension + matchedExtension,
159+
}];
160+
});
161+
}),
162+
);
163+
return allExpansions.flat();
164+
}
165+
166+
// Non-root-level patterns: find matching src:dist pair based on directory prefix
84167
const srcdistMatch = sortedByDistLength.find(
85-
({ dist }) => outputPrefix === dist || outputPrefix.startsWith(dist),
168+
({ dist }) => directoryPrefix === dist || directoryPrefix.startsWith(dist),
86169
);
87170

88171
if (!srcdistMatch) {
@@ -91,19 +174,32 @@ export const expandBuildOutputWildcards = async (
91174

92175
const srcPath = path.posix.join(
93176
srcdistMatch.srcResolved,
94-
outputPrefix.slice(srcdistMatch.dist.length),
177+
directoryPrefix.slice(srcdistMatch.dist.length),
95178
);
179+
96180
const allFiles = await getDirectoryFiles(srcPath);
97181
return allFiles.flatMap((filePath) => {
98-
const match = extractWildcardMatch(filePath, patternParts);
99-
if (match) {
182+
if (!matchesPattern(filePath)) {
183+
return [];
184+
}
185+
186+
// Strip source extension before appending output extension
187+
const filePathWithoutExtension = filePath.replace(extensionPattern, '');
188+
189+
// For multiple wildcard patterns, use the matched path from extractWildcardMatch
190+
if (patternParts.length > 1) {
191+
const match = extractWildcardMatch(filePathWithoutExtension, patternParts);
100192
return [{
101193
...output,
102-
outputPath: outputPrefix + match + matchedExtension,
194+
outputPath: directoryPrefix + match + matchedExtension,
103195
}];
104196
}
105197

106-
return [];
198+
// For single wildcard patterns, preserve the full relative path
199+
return [{
200+
...output,
201+
outputPath: directoryPrefix + filePathWithoutExtension + matchedExtension,
202+
}];
107203
});
108204
}),
109205
);

0 commit comments

Comments
 (0)