diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index 151cf1ec2f719..466333a9828e8 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -1842,7 +1842,7 @@ pub const PosixToWinNormalizer = struct { // underlying implementation: - fn resolveWithExternalBuf( + pub fn resolveWithExternalBuf( buf: *Buf, source_dir: []const u8, maybe_posix_path: []const u8, @@ -1856,11 +1856,16 @@ pub const PosixToWinNormalizer = struct { const source_root = windowsFilesystemRoot(source_dir); @memcpy(buf[0..source_root.len], source_root); @memcpy(buf[source_root.len..][0 .. maybe_posix_path.len - 1], maybe_posix_path[1..]); - const res = buf[0 .. source_root.len + maybe_posix_path.len - 1]; + buf[source_root.len + maybe_posix_path.len - 1] = 0; + const res = buf[0 .. source_root.len + maybe_posix_path.len - 1 :0]; assert(!bun.strings.isWindowsAbsolutePathMissingDriveLetter(u8, res)); - return res; + return ensureLongPath(buf, res); } } + + @memcpy(buf.ptr, maybe_posix_path); + buf[maybe_posix_path.len] = 0; + return ensureLongPath(buf, buf[0..maybe_posix_path.len :0]); } return maybe_posix_path; } @@ -1881,11 +1886,16 @@ pub const PosixToWinNormalizer = struct { const source_root = windowsFilesystemRoot(cwd); assert(source_root.ptr == source_root.ptr); @memcpy(buf[source_root.len..][0 .. maybe_posix_path.len - 1], maybe_posix_path[1..]); - const res = buf[0 .. source_root.len + maybe_posix_path.len - 1]; + buf[source_root.len + maybe_posix_path.len - 1] = 0; + const res = buf[0 .. source_root.len + maybe_posix_path.len - 1 :0]; assert(!bun.strings.isWindowsAbsolutePathMissingDriveLetter(u8, res)); - return res; + return ensureLongPath(buf, res); } } + + @memcpy(buf.ptr, maybe_posix_path); + buf[maybe_posix_path.len] = 0; + return ensureLongPath(buf, buf[0..maybe_posix_path.len :0]); } return maybe_posix_path; @@ -1910,15 +1920,47 @@ pub const PosixToWinNormalizer = struct { buf[source_root.len + maybe_posix_path.len - 1] = 0; const res = buf[0 .. source_root.len + maybe_posix_path.len - 1 :0]; assert(!bun.strings.isWindowsAbsolutePathMissingDriveLetter(u8, res)); - return res; + return ensureLongPath(buf, res); } } + + @memcpy(buf.ptr, maybe_posix_path); + buf[maybe_posix_path.len] = 0; + return ensureLongPath(buf, buf[0..maybe_posix_path.len :0]); } @memcpy(buf.ptr, maybe_posix_path); buf[maybe_posix_path.len] = 0; return buf[0..maybe_posix_path.len :0]; } + + /// `res` needs to exist in `buf` and needs to start at the beginning + pub fn ensureLongPath(buf: anytype, res: [:0]const u8) [:0]u8 { + assert(buf.ptr == res.ptr); + if (bun.Environment.isWindows) { + const long_path_len = bun.windows.GetLongPathNameA( + @ptrCast(buf.ptr), + // it's safe to use the same buffer + @ptrCast(buf.ptr), + @intCast(buf.len), + ); + + if (long_path_len == 0) { + // Length 0 means there was an error. This could happen + // if the file/dir does not exist, or permissions are missing + // for any component in the path. In this case, ignore the + // error and return the normalized path. + // + // If it doesn't exist, but the 8.3 path does, it will resolve + // correctly because the dir cache will cache it. + return buf[0..res.len :0]; + } + + return buf[0..long_path_len :0]; + } + + return buf[0..res.len :0]; + } }; /// Used in PathInlines.h diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 34dec389f1c38..b29a102a6d772 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -1160,8 +1160,11 @@ pub const Resolver = struct { // Treating these paths as absolute paths on all platforms means Windows // users will not be able to accidentally make use of these paths. if (std.fs.path.isAbsolute(import_path)) { + var normalizer = ResolvePath.PosixToWinNormalizer{}; + const abs_import_path = normalizer.resolve(source_dir, import_path); + if (r.debug_logs) |*debug| { - debug.addNoteFmt("The import \"{s}\" is being treated as an absolute path", .{import_path}); + debug.addNoteFmt("The import \"{s}\" is being treated as an absolute path", .{abs_import_path}); } // First, check path overrides from the nearest enclosing TypeScript "tsconfig.json" file @@ -1169,7 +1172,7 @@ pub const Resolver = struct { const dir_info: *DirInfo = _dir_info; if (dir_info.enclosing_tsconfig_json) |tsconfig| { if (tsconfig.paths.count() > 0) { - if (r.matchTSConfigPaths(tsconfig, import_path, kind)) |res| { + if (r.matchTSConfigPaths(tsconfig, abs_import_path, kind)) |res| { // We don't set the directory fd here because it might remap an entirely different directory return .{ @@ -1187,26 +1190,25 @@ pub const Resolver = struct { } } - if (r.opts.external.abs_paths.count() > 0 and r.opts.external.abs_paths.contains(import_path)) { + if (r.opts.external.abs_paths.count() > 0 and r.opts.external.abs_paths.contains(abs_import_path)) { // If the string literal in the source text is an absolute path and has // been marked as an external module, mark it as *not* an absolute path. // That way we preserve the literal text in the output and don't generate // a relative path from the output directory to that path. if (r.debug_logs) |*debug| { - debug.addNoteFmt("The path \"{s}\" is marked as external by the user", .{import_path}); + debug.addNoteFmt("The path \"{s}\" is marked as external by the user", .{abs_import_path}); } return .{ .success = Result{ - .path_pair = .{ .primary = Path.init(import_path) }, + .path_pair = .{ .primary = Path.init(abs_import_path) }, .is_external = true, }, }; } // Run node's resolution rules (e.g. adding ".js") - var normalizer = ResolvePath.PosixToWinNormalizer{}; - if (r.loadAsFileOrDirectory(normalizer.resolve(source_dir, import_path), kind)) |entry| { + if (r.loadAsFileOrDirectory(abs_import_path, kind)) |entry| { return .{ .success = Result{ .dirname_fd = entry.dirname_fd, @@ -1230,7 +1232,10 @@ pub const Resolver = struct { if (check_relative) { const parts = [_]string{ source_dir, import_path }; - const abs_path = r.fs.absBuf(&parts, bufs(.relative_abs_path)); + const joined = r.fs.absBufZ(&parts, bufs(.relative_abs_path)); + const abs_path: []u8 = ResolvePath.PosixToWinNormalizer.ensureLongPath(bufs(.relative_abs_path), joined); + // var normalizer = ResolvePath.PosixToWinNormalizer{}; + // const abs_path = normalizer.resolve(source_dir, joined); if (r.opts.external.abs_paths.count() > 0 and r.opts.external.abs_paths.contains(abs_path)) { // If the string literal in the source text is an absolute path and has @@ -2437,7 +2442,9 @@ pub const Resolver = struct { return r.loadNodeModules(import_path, kind, source_dir_info, global_cache, false); } else { const paths = [_]string{ source_dir_info.abs_path, import_path }; - const resolved = r.fs.absBuf(&paths, bufs(.resolve_without_remapping)); + const joined = r.fs.absBuf(&paths, bufs(.resolve_without_remapping)); + var normalizer = ResolvePath.PosixToWinNormalizer{}; + const resolved = normalizer.resolve(source_dir_info.abs_path, joined); if (r.loadAsFileOrDirectory(resolved, kind)) |result| { return .{ .success = result }; } diff --git a/src/windows.zig b/src/windows.zig index 902110d7189b0..58e9d05e97860 100644 --- a/src/windows.zig +++ b/src/windows.zig @@ -3405,6 +3405,18 @@ pub extern "kernel32" fn GetThreadDescription( *PWSTR, // [out] ) std.os.windows.HRESULT; +pub extern "kernel32" fn GetLongPathNameW( + lpszShortPath: LPCWSTR, + lpszLongPath: LPWSTR, + cchBuffer: DWORD, +) DWORD; + +pub extern "kernel32" fn GetLongPathNameA( + lpszShortPath: LPCSTR, + lpszLongPath: LPSTR, + cchBuffer: DWORD, +) DWORD; + pub const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x200; pub const ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002; pub const ENABLE_PROCESSED_OUTPUT = 0x0001; diff --git a/test/bundler/fixtures/with-assets/img.png b/test/bundler/fixtures/with-assets/img.png index 7c48c8d6a027c..0dd1608e45a9c 120000 Binary files a/test/bundler/fixtures/with-assets/img.png and b/test/bundler/fixtures/with-assets/img.png differ diff --git a/test/js/bun/resolve/longjavascriptfilename.js b/test/js/bun/resolve/longjavascriptfilename.js new file mode 100644 index 0000000000000..bbf2a2836f33d --- /dev/null +++ b/test/js/bun/resolve/longjavascriptfilename.js @@ -0,0 +1 @@ +module.exports.foo = "bar"; diff --git a/test/js/bun/resolve/resolve.test.ts b/test/js/bun/resolve/resolve.test.ts index 1a46b049cdabb..bd6225dbd3e04 100644 --- a/test/js/bun/resolve/resolve.test.ts +++ b/test/js/bun/resolve/resolve.test.ts @@ -312,3 +312,7 @@ it.todo("import override to bun:test", async () => { // @ts-expect-error expect(await import("#bun_test")).toBeDefined(); }); + +it("should import short paths", () => { + expect(require("./LONGJA~1.JS")).toBeDefined(); +});