@@ -24,6 +24,7 @@ const CHAR_UPPERCASE_A = 65
2424const CHAR_UPPERCASE_Z = 90
2525
2626// Regular expressions.
27+ const msysDriveRegExp = / ^ \/ ( [ a - z A - Z ] ) ( \/ | $ ) /
2728const slashRegExp = / [ / \\ ] /
2829const nodeModulesPathRegExp = / (?: ^ | [ / \\ ] ) n o d e _ m o d u l e s (?: [ / \\ ] | $ ) /
2930
@@ -112,6 +113,17 @@ function getUrl() {
112113 return _url as typeof import ( 'node:url' )
113114}
114115
116+ // On Windows, convert MSYS drive notation to native: /c/path → C:/path
117+ function msysDriveToNative ( normalized : string ) : string {
118+ if ( WIN32 ) {
119+ return normalized . replace (
120+ msysDriveRegExp ,
121+ ( _ , letter , sep ) => `${ letter . toUpperCase ( ) } :${ sep || '/' } ` ,
122+ )
123+ }
124+ return normalized
125+ }
126+
115127/**
116128 * Check if a path contains node_modules directory.
117129 *
@@ -359,58 +371,82 @@ export function isRelative(pathLike: string | Buffer | URL): boolean {
359371}
360372
361373/**
362- * Convert Unix-style POSIX paths (MSYS/Git Bash format) back to native Windows paths.
374+ * Check if a path uses MSYS/Git Bash Unix-style drive letter notation.
375+ *
376+ * Detects paths in the format `/c/...` where a single letter after the leading
377+ * slash represents a Windows drive letter. These paths are produced by MSYS2,
378+ * Git Bash, and `command -v` on Windows.
379+ *
380+ * Detection rules:
381+ * - Must start with `/` followed by a single ASCII letter (a-z, A-Z)
382+ * - The letter must be followed by `/` or be at the end of the string
383+ * - Examples: `/c/Users/name`, `/d/projects`, `/c`
384+ * - Non-matches: `/tmp`, `/usr/local`, `C:/Windows`
385+ *
386+ * @param {string | Buffer | URL } pathLike - The path to check
387+ * @returns {boolean } `true` if the path uses MSYS drive letter notation
388+ *
389+ * @example
390+ * ```typescript
391+ * // MSYS drive letter paths
392+ * isUnixPath('/c/Users/name') // true
393+ * isUnixPath('/d/projects/app') // true
394+ * isUnixPath('/c') // true
395+ * isUnixPath('/C/Windows') // true
396+ *
397+ * // Not MSYS drive paths
398+ * isUnixPath('/tmp/build') // false
399+ * isUnixPath('/usr/local/bin') // false
400+ * isUnixPath('C:/Windows') // false
401+ * isUnixPath('./relative') // false
402+ * isUnixPath('') // false
403+ * ```
404+ */
405+ /*@__NO_SIDE_EFFECTS__ */
406+ export function isUnixPath ( pathLike : string | Buffer | URL ) : boolean {
407+ const filepath = pathLikeToString ( pathLike )
408+ return typeof filepath === 'string' && msysDriveRegExp . test ( filepath )
409+ }
410+
411+ /**
412+ * Convert Unix-style POSIX paths to native Windows paths.
363413 *
364- * This is the inverse of {@link toUnixPath}. MSYS-style paths use `/c/` notation
365- * for drive letters, which PowerShell and cmd.exe cannot resolve. This function
366- * converts them back to native Windows format.
414+ * This is the inverse of {@link toUnixPath}. On Windows, MSYS-style paths use
415+ * `/c/` notation for drive letters and forward slashes, which PowerShell and
416+ * cmd.exe cannot resolve. This function converts them to native Windows format
417+ * with backslashes and proper drive letters.
367418 *
368419 * Conversion rules:
369- * - On Windows: Converts Unix drive notation to Windows drive letters
370- * - `/c/path/to/file` becomes `C:/path/to/file`
371- * - `/d/projects/app` becomes `D:/projects/app`
420+ * - On Windows: Converts drive notation and separators to native format
421+ * - `/c/path/to/file` becomes `C:\path\to\file`
422+ * - `/d/projects/app` becomes `D:\projects\app`
423+ * - Forward slashes become backslashes
372424 * - Drive letters are always uppercase in the output
373- * - On Unix: Returns the path unchanged (passes through normalization)
374- *
375- * This is particularly important for:
376- * - GitHub Actions runners where `command -v` returns MSYS paths
377- * - Tools like sfw that need to resolve real binary paths on Windows
378- * - Scripts that receive paths from Git Bash but need to pass them to native Windows tools
425+ * - On Unix: Returns the normalized path unchanged (forward slashes preserved)
379426 *
380427 * @param {string | Buffer | URL } pathLike - The MSYS/Unix-style path to convert
381- * @returns {string } Native Windows path (e.g., `C:/ path/to/ file`) or normalized Unix path
428+ * @returns {string } Native Windows path (e.g., `C:\ path\to\ file`) or normalized Unix path
382429 *
383430 * @example
384431 * ```typescript
385- * // MSYS drive letter paths
386- * fromUnixPath('/c/projects/app/file.txt') // 'C:/projects/app/file.txt'
387- * fromUnixPath('/d/projects/foo/bar') // 'D:/projects/foo/bar'
388- *
389- * // Non-drive Unix paths (unchanged)
390- * fromUnixPath('/tmp/build/output') // '/tmp/build/output'
391- * fromUnixPath('/usr/local/bin') // '/usr/local/bin'
432+ * // MSYS drive letter paths (Windows)
433+ * fromUnixPath('/c/projects/app/file.txt') // 'C:\\projects\\app\\file.txt'
434+ * fromUnixPath('/d/projects/foo/bar') // 'D:\\projects\\foo\\bar'
435+ * fromUnixPath('/c') // 'C:\\'
392436 *
393- * // Already Windows paths (unchanged )
394- * fromUnixPath('C:/Windows/System32') // 'C:/ Windows/ System32'
437+ * // Forward-slash paths (Windows )
438+ * fromUnixPath('C:/Windows/System32') // 'C:\\ Windows\\ System32'
395439 *
396- * // Edge cases
397- * fromUnixPath('/c') // 'C:/'
398- * fromUnixPath('') // '.'
440+ * // Unix (unchanged, forward slashes preserved)
441+ * fromUnixPath('/tmp/build/output') // '/tmp/build/output'
399442 * ```
400443 */
401444/*@__NO_SIDE_EFFECTS__ */
402445export function fromUnixPath ( pathLike : string | Buffer | URL ) : string {
403446 const normalized = normalizePath ( pathLike )
404-
405- // On Windows, convert MSYS drive notation back to native: /c/path → C:/path
406447 if ( WIN32 ) {
407- return normalized . replace (
408- / ^ \/ ( [ a - z A - Z ] ) ( \/ | $ ) / ,
409- ( _ , letter , sep ) => `${ letter . toUpperCase ( ) } :${ sep || '/' } ` ,
410- )
448+ return normalized . replace ( / \/ / g, '\\' )
411449 }
412-
413- // On Unix, just return the normalized path
414450 return normalized
415451}
416452
@@ -427,6 +463,7 @@ export function fromUnixPath(pathLike: string | Buffer | URL): string {
427463 * - Returns `.` for empty or collapsed paths
428464 *
429465 * Special handling:
466+ * - MSYS drive letters (Windows only): `/c/path` becomes `C:/path`
430467 * - UNC paths: Maintains double leading slashes for `//server/share` format
431468 * - Windows namespaces: Preserves `//./` and `//?/` prefixes
432469 * - Leading `..` segments: Preserved in relative paths without prefix
@@ -453,6 +490,10 @@ export function fromUnixPath(pathLike: string | Buffer | URL): string {
453490 * normalizePath('C:\\Users\\username\\file.txt') // 'C:/Users/username/file.txt'
454491 * normalizePath('foo\\bar\\baz') // 'foo/bar/baz'
455492 *
493+ * // MSYS drive letters (Windows only)
494+ * normalizePath('/c/projects/app') // 'C:/projects/app' (on Windows)
495+ * normalizePath('/d/Users/name') // 'D:/Users/name' (on Windows)
496+ *
456497 * // UNC paths
457498 * normalizePath('\\\\server\\share\\file') // '//server/share/file'
458499 *
@@ -592,7 +633,7 @@ export function normalizePath(pathLike: string | Buffer | URL): string {
592633 if ( segment === '..' ) {
593634 return prefix ? prefix . slice ( 0 , - 1 ) || '/' : '..'
594635 }
595- return prefix + segment
636+ return msysDriveToNative ( prefix + segment )
596637 }
597638 // Process segments and handle '.', '..', and empty segments.
598639 let collapsed = ''
@@ -688,7 +729,7 @@ export function normalizePath(pathLike: string | Buffer | URL): string {
688729 if ( collapsed . length === 0 ) {
689730 return prefix || '.'
690731 }
691- return prefix + collapsed
732+ return msysDriveToNative ( prefix + collapsed )
692733}
693734
694735/**
0 commit comments