diff --git a/.gitignore b/.gitignore index f543c3aefe..064d2c21b2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ packages/*/__tests__/_temp/ .DS_Store *.xar packages/*/audit.json +.nx/ diff --git a/packages/glob/__tests__/windows-path-matching.test.ts b/packages/glob/__tests__/windows-path-matching.test.ts new file mode 100644 index 0000000000..99b74d24e0 --- /dev/null +++ b/packages/glob/__tests__/windows-path-matching.test.ts @@ -0,0 +1,83 @@ +/** + * Test to validate that glob works correctly on Windows with backslash paths + * This test validates the fix for glob not working on GitHub's Windows runners + */ + +import {MatchKind} from '../src/internal-match-kind' +import {Pattern} from '../src/internal-pattern' + +const IS_WINDOWS = process.platform === 'win32' + +describe('Windows path matching', () => { + it('matches paths with backslashes on Windows', () => { + if (!IS_WINDOWS) { + // This test is only relevant on Windows + return + } + + // Test basic pattern matching with Windows paths + const pattern = new Pattern('C:\\Users\\test\\*') + + // The itemPath would come from fs.readdir with backslashes on Windows + const itemPath = 'C:\\Users\\test\\file.txt' + + // This should match because the pattern and path both refer to the same file + expect(pattern.match(itemPath)).toBe(MatchKind.All) + }) + + it('partial matches work with backslashes on Windows', () => { + if (!IS_WINDOWS) { + return + } + + // Test partial matching with Windows paths + const pattern = new Pattern('C:\\Users\\test\\**') + + // Should partially match parent directory + expect(pattern.partialMatch('C:\\Users')).toBe(true) + expect(pattern.partialMatch('C:\\Users\\test')).toBe(true) + }) + + it('matches globstar patterns with backslashes on Windows', () => { + if (!IS_WINDOWS) { + return + } + + const pattern = new Pattern('C:\\foo\\**') + + // Should match the directory itself and descendants + expect(pattern.match('C:\\foo')).toBe(MatchKind.All) + expect(pattern.match('C:\\foo\\bar')).toBe(MatchKind.All) + expect(pattern.match('C:\\foo\\bar\\baz.txt')).toBe(MatchKind.All) + }) + + it('matches wildcard patterns with mixed separators on Windows', () => { + if (!IS_WINDOWS) { + return + } + + // Pattern might be specified with forward slashes by user + const pattern = new Pattern('C:/Users/*/file.txt') + + // But the actual path from filesystem will have backslashes + expect(pattern.match('C:\\Users\\test\\file.txt')).toBe(MatchKind.All) + }) + + it('handles complex patterns with backslashes on Windows', () => { + if (!IS_WINDOWS) { + return + } + + const currentDrive = process.cwd().substring(0, 2) + const pattern = new Pattern(`${currentDrive}\\**\\*.txt`) + + // Should match .txt files at any depth + expect(pattern.match(`${currentDrive}\\file.txt`)).toBe(MatchKind.All) + expect(pattern.match(`${currentDrive}\\foo\\bar\\test.txt`)).toBe( + MatchKind.All + ) + expect(pattern.match(`${currentDrive}\\foo\\bar\\test.js`)).toBe( + MatchKind.None + ) + }) +}) diff --git a/packages/glob/src/internal-pattern.ts b/packages/glob/src/internal-pattern.ts index e1dbbda843..cd92efabb3 100644 --- a/packages/glob/src/internal-pattern.ts +++ b/packages/glob/src/internal-pattern.ts @@ -156,6 +156,10 @@ export class Pattern { itemPath = pathHelper.safeTrimTrailingSeparator(itemPath) } + // Convert to forward slashes on Windows before matching with minimatch + // since the pattern was converted to forward slashes in the constructor + itemPath = Pattern.convertToMinimatchPath(itemPath) + // Match if (this.minimatch.match(itemPath)) { return this.trailingSeparator ? MatchKind.Directory : MatchKind.All @@ -176,8 +180,11 @@ export class Pattern { return this.rootRegExp.test(itemPath) } + // Convert to forward slashes on Windows to match the pattern format used by minimatch + itemPath = Pattern.convertToMinimatchPath(itemPath) + return this.minimatch.matchOne( - itemPath.split(IS_WINDOWS ? /\\+/ : /\/+/), + itemPath.split(/\/+/), this.minimatch.set[0], true ) @@ -193,6 +200,18 @@ export class Pattern { .replace(/\*/g, '[*]') // escape '*' } + /** + * Converts path to forward slashes on Windows for compatibility with minimatch. + * On Windows, minimatch patterns use forward slashes, so paths must be converted + * to match the same format. + */ + private static convertToMinimatchPath(itemPath: string): string { + if (IS_WINDOWS) { + return itemPath.replace(/\\/g, '/') + } + return itemPath + } + /** * Normalizes slashes and ensures absolute root */