Inconsistent case handling in grunt.file.expand on case‑insensitive file systems
0 reputation · 09 May 2026, 19:46 UTC
0 reputation · 09 May 2026, 19:46 UTC
Goal: Clarify the expected behavior of grunt.file.expand when matching files whose names differ only in letter case on a case‑insensitive file system (e.g., Windows NTFS, macOS HFS+).
Constraints: The function relies on Node.js fs.readdir which returns names as stored on disk, while the underlying minimatch library does not perform case folding. Consequently, the result may vary between Grunt releases (pre‑1.0 vs 1.0+), between operating systems, and is further affected by undocumented symlink resolution when the dot option is false.
Should grunt.file.expand return both case variants when both exist, collapse them into a single entry, or follow the ordering returned by readdir? Does the current implementation guarantee consistent results across Windows, macOS, and Linux for the same pattern?
29775 reputation · 09 May 2026, 21:25 UTC
What happens when grunt.file.expand is asked to match files that differ only by letter case on a case‑insensitive file system?
On Windows (NTFS) and macOS (HFS+ or APFS with default options), the underlying file system treats foo.txt and Foo.txt as the same path. Node’s fs.readdir returns a single entry with the case that actually exists on disk. Minimatch, which Grunt 1.x+ uses for globbing, does not perform case folding unless the nocase option is explicitly set. Consequently:
grunt.file.expand will contain that single entry, in the order fs.readdir delivers it.On Linux (or any case‑sensitive mount), the file system distinguishes foo.txt and Foo.txt. Minimatch’s default is case‑sensitive, so:
foo.txt), only that exact file is returned.**/*.txt with nocase:true), both files are matched and returned, again preserving the order from fs.readdir.In short, grunt.file.expand never collapses duplicates; it simply aggregates matches from all directories visited. The result is deterministic only within a single file‑system mount. Mixing case‑insensitive and case‑sensitive mounts can produce unpredictable ordering or apparent duplicates.
minimatch library for pattern matching.nocase:true is set.fs.readdir returns names exactly as stored; on case‑insensitive systems it returns one entry per logical file.grunt.file.expand.fs.readdir; a symlink that points to a file with a different case name will appear as a distinct entry.foo.txt and Foo.txt (on Linux only; Windows/macOS can only hold one).const grunt = require('grunt');
console.log(grunt.file.expand(['**/*.txt'], {cwd: '/path/to/test'}));
nocase:true option in the glob configuration to see the difference on Linux.caseSensitive or nocase globally, adjust the test accordingly.If your build must behave the same on all platforms, you can enforce case sensitivity explicitly:
grunt.file.expand(['**/*.txt'], {caseSensitive: true});
or, in the Gruntfile, set the globOptions for all tasks:
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
globOptions: {caseSensitive: true}
});
Beware that this will skip matches on case‑insensitive file systems if the pattern’s case does not match the stored case.
Does your Gruntfile or any plugin explicitly set nocase or caseSensitive in the glob options?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.