diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index 48c03939b6dc88..57f4201dfe2e2b 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -471,6 +471,9 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * moduleObject->sourceCode.set(vm, moduleObject, jsSourceCode); auto index = filenameString.reverseFind(PLATFORM_SEP, filenameString.length()); + // filenameString is coming from js, any separator could be used + if (index == WTF::notFound) + index = filenameString.reverseFind(NOT_PLATFORM_SEP, filenameString.length()); String dirnameString; if (index != WTF::notFound) { dirnameString = filenameString.substring(0, index); diff --git a/src/bun.js/bindings/PathInlines.h b/src/bun.js/bindings/PathInlines.h index eb8495563dda23..26c0314f9df435 100644 --- a/src/bun.js/bindings/PathInlines.h +++ b/src/bun.js/bindings/PathInlines.h @@ -4,9 +4,13 @@ #if OS(WINDOWS) #define PLATFORM_SEP_s "\\"_s #define PLATFORM_SEP '\\' +#define NOT_PLATFORM_SEP_s "/"_s +#define NOT_PLATFORM_SEP '/' #else #define PLATFORM_SEP_s "/"_s #define PLATFORM_SEP '/' +#define NOT_PLATFORM_SEP_s "\\"_s +#define NOT_PLATFORM_SEP '\\' #endif ALWAYS_INLINE bool isAbsolutePath(WTF::String input) diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index 4a7df06662c825..f456af7aadd32d 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -578,15 +578,27 @@ pub fn windowsFilesystemRoot(path: []const u8) []const u8 { return windowsFilesystemRootT(u8, path); } +pub fn isDriveLetter(c: u8) bool { + return isDriveLetterT(u8, c); +} + +pub fn isDriveLetterT(comptime T: type, c: T) bool { + return 'a' <= c and c <= 'z' or 'A' <= c and c <= 'Z'; +} + // path.relative lets you do relative across different share drives pub fn windowsFilesystemRootT(comptime T: type, path: []const T) []const T { - if (path.len < 3) + // minimum: `C:` + if (path.len < 2) return if (isSepAnyT(T, path[0])) path[0..1] else path[0..0]; // with drive letter const c = path[0]; - if (path[1] == ':' and isSepAnyT(T, path[2])) { - if ('a' <= c and c <= 'z' or 'A' <= c and c <= 'Z') { - return path[0..3]; + if (path[1] == ':') { + if (isDriveLetterT(T, c)) { + if (path.len > 2 and isSepAnyT(T, path[2])) + return path[0..3] + else + return path[0..2]; } } // UNC @@ -759,7 +771,7 @@ pub fn normalizeStringGenericT( if (preserve_trailing_slash) { // Was there a trailing slash? Let's keep it. - if (buf_i > 0 and path_[path_.len - 1] == separator and buf[buf_i] != separator) { + if (buf_i > 0 and path_[path_.len - 1] == separator and buf[buf_i - 1] != separator) { buf[buf_i] = separator; buf_i += 1; } @@ -1004,7 +1016,7 @@ pub fn normalizeStringBuf( buf: []u8, comptime allow_above_root: bool, comptime _platform: Platform, - comptime preserve_trailing_slash: anytype, + comptime preserve_trailing_slash: bool, ) []u8 { return normalizeStringBufT(u8, str, buf, allow_above_root, _platform, preserve_trailing_slash); } @@ -1015,7 +1027,7 @@ pub fn normalizeStringBufT( buf: []T, comptime allow_above_root: bool, comptime _platform: Platform, - comptime preserve_trailing_slash: anytype, + comptime preserve_trailing_slash: bool, ) []T { const platform = comptime _platform.resolve(); diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index 448c9956335030..33d01186599e7d 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -3277,7 +3277,17 @@ pub const Resolver = struct { defer sliced.deinit(); const str = brk: { - if (std.fs.path.isAbsolute(sliced.slice())) break :brk sliced.slice(); + if (std.fs.path.isAbsolute(sliced.slice())) { + if (comptime Environment.isWindows) { + const dir_path_buf = bufs(.node_modules_paths_buf); + var normalizer = bun.path.PosixToWinNormalizer{}; + const normalized = normalizer.resolveCWD(sliced.slice()) catch { + @panic("Failed to get cwd for _nodeModulesPaths"); + }; + break :brk bun.path.normalizeBuf(normalized, dir_path_buf, .windows); + } + break :brk sliced.slice(); + } const dir_path_buf = bufs(.node_modules_paths_buf); break :brk bun.path.joinStringBuf(dir_path_buf, &[_]string{ r.fs.top_level_dir, sliced.slice() }, .auto); }; @@ -3292,10 +3302,10 @@ pub const Resolver = struct { const path_without_trailing_slash = strings.withoutTrailingSlash(dir_info.abs_path); const path_parts = brk: { if (path_without_trailing_slash.len == 1 and path_without_trailing_slash[0] == '/') { - break :brk [2]string{ "", "/node_modules" }; + break :brk [2]string{ "", std.fs.path.sep_str ++ "node_modules" }; } - break :brk [2]string{ path_without_trailing_slash, "/node_modules" }; + break :brk [2]string{ path_without_trailing_slash, std.fs.path.sep_str ++ "node_modules" }; }; const nodemodules_path = bun.strings.concat(stack_fallback_allocator.get(), &path_parts) catch unreachable; bun.path.posixToPlatformInPlace(u8, nodemodules_path); @@ -3305,7 +3315,7 @@ pub const Resolver = struct { } else { // does not exist const full_path = std.fs.path.resolve(r.allocator, &[1][]const u8{str}) catch unreachable; - var path = full_path; + var path = strings.withoutTrailingSlash(full_path); while (true) { const path_without_trailing_slash = strings.withoutTrailingSlash(path); @@ -3315,13 +3325,13 @@ pub const Resolver = struct { stack_fallback_allocator.get(), &[_]string{ path_without_trailing_slash, - "/node_modules", + std.fs.path.sep_str ++ "node_modules", }, ) catch unreachable, ), ) catch unreachable; - path = path[0 .. strings.lastIndexOfChar(path, '/') orelse break]; + path = path[0 .. strings.lastIndexOfChar(path, std.fs.path.sep) orelse break]; } } diff --git a/test/js/node/module/node-module-module.test.js b/test/js/node/module/node-module-module.test.js index c5453e4ede099d..52c201d5c52779 100644 --- a/test/js/node/module/node-module-module.test.js +++ b/test/js/node/module/node-module-module.test.js @@ -1,6 +1,5 @@ -// @known-failing-on-windows: 1 failing import { expect, test } from "bun:test"; -import { bunEnv, bunExe } from "harness"; +import { bunEnv, bunExe, ospath } from "harness"; import { _nodeModulePaths, builtinModules, isBuiltin, wrap } from "module"; import Module from "module"; import path from "path"; @@ -37,21 +36,26 @@ test("module.Module works", () => { }); test("_nodeModulePaths() works", () => { + const root = process.platform === "win32" ? "C:\\" : "/"; expect(() => { _nodeModulePaths(); }).toThrow(); expect(_nodeModulePaths(".").length).toBeGreaterThan(0); - expect(_nodeModulePaths(".").pop()).toBe("/node_modules"); + expect(_nodeModulePaths(".").pop()).toBe(root + "node_modules"); expect(_nodeModulePaths("")).toEqual(_nodeModulePaths(".")); - expect(_nodeModulePaths("/")).toEqual(["/node_modules"]); + expect(_nodeModulePaths("/")).toEqual([root + "node_modules"]); expect(_nodeModulePaths("/a/b/c/d")).toEqual([ - "/a/b/c/d/node_modules", - "/a/b/c/node_modules", - "/a/b/node_modules", - "/a/node_modules", - "/node_modules", + ospath(root + "a/b/c/d/node_modules"), + ospath(root + "a/b/c/node_modules"), + ospath(root + "a/b/node_modules"), + ospath(root + "a/node_modules"), + ospath(root + "node_modules"), + ]); + expect(_nodeModulePaths("/a/b/../d")).toEqual([ + ospath(root + "a/d/node_modules"), + ospath(root + "a/node_modules"), + ospath(root + "node_modules"), ]); - expect(_nodeModulePaths("/a/b/../d")).toEqual(["/a/d/node_modules", "/a/node_modules", "/node_modules"]); }); test("Module.wrap", () => {