-
Notifications
You must be signed in to change notification settings - Fork 5.1k
feat: Implements the Module.findPackageJSON function Node.js
#24098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b5dd396
0abeef9
2dc3e8e
d91bd21
6692eb6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2969,3 +2969,75 @@ const typeBaseNameT = bun.meta.typeBaseNameT; | |||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const strings = bun.strings; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const L = strings.literal; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Find the nearest package.json file starting from a given path using the resolver cache | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Returns the absolute path to the package.json, or an empty string if not found | ||||||||||||||||||||||||||||||||||||||||||||||||||
| export fn Bun__findPackageJSON(globalObject: *jsc.JSGlobalObject, input_path: *bun.String, result: *bun.String) void { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| var slice = input_path.toUTF8(bun.default_allocator); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| defer slice.deinit(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| var current_dir = slice.slice(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (current_dir.len == 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| result.* = bun.String.empty; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2975
to
+2983
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate that the input path is absolute. The function documentation and PR objectives state that Consider adding validation after line 2983: var current_dir = slice.slice();
if (current_dir.len == 0) {
result.* = bun.String.empty;
return;
}
+
+ // Validate that the input is an absolute path
+ if (!isAbsolutePosixT(u8, current_dir) and !isAbsoluteWindowsT(u8, current_dir)) {
+ result.* = bun.String.empty;
+ return;
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // If the input is a file, start from its directory | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if it's a regular file by trying to get parent directory | ||||||||||||||||||||||||||||||||||||||||||||||||||
| var path_buf_z: [bun.MAX_PATH_BYTES:0]u8 = undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (current_dir.len >= path_buf_z.len) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Path too long to process | ||||||||||||||||||||||||||||||||||||||||||||||||||
| result.* = bun.String.empty; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| @memcpy(path_buf_z[0..current_dir.len], current_dir); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| path_buf_z[current_dir.len] = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const current_dir_z = path_buf_z[0..current_dir.len :0]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const stat_result = bun.sys.stat(current_dir_z); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (stat_result == .result) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const mode = stat_result.result.mode; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const S = bun.S; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if it's a regular file (not a directory) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if ((mode & S.IFMT) == S.IFREG) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| current_dir = if (Environment.isWindows) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| dirnameWindowsT(u8, current_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||
| dirnamePosixT(u8, current_dir); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Heuristic: if path doesn't exist and doesn't end with a separator, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // treat it as a file path and start from its dirname. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (current_dir.len > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const last = current_dir[current_dir.len - 1]; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if ((Environment.isWindows and (last != CHAR_BACKWARD_SLASH and last != CHAR_FORWARD_SLASH)) or | ||||||||||||||||||||||||||||||||||||||||||||||||||
| (!Environment.isWindows and last != CHAR_FORWARD_SLASH)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| current_dir = if (Environment.isWindows) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| dirnameWindowsT(u8, current_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||
| dirnamePosixT(u8, current_dir); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use the resolver's DirInfo cache to find package.json | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const bun_vm = globalObject.bunVM(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const resolver = &bun_vm.transpiler.resolver; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Walk up the directory tree using the resolver cache | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use std.fs.path.dirname to get null once we reach the root | ||||||||||||||||||||||||||||||||||||||||||||||||||
| var search_dir: ?[]const u8 = current_dir; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| while (search_dir) |dir| : (search_dir = std.fs.path.dirname(dir)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (resolver.dirInfoCached(dir) catch null) |dir_info| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if (dir_info.package_json) |pkg_json| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| result.* = bun.String.cloneUTF8(pkg_json.source.path.text); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // Not found | ||||||||||||||||||||||||||||||||||||||||||||||||||
| result.* = bun.String.empty; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { describe, test, expect } from "bun:test"; | ||
| import { findPackageJSON } from "node:module"; | ||
| import { pathToFileURL } from "node:url"; | ||
| import path from "node:path"; | ||
|
|
||
| describe.concurrent("Module.findPackageJSON", () => { | ||
| test.concurrent("finds package.json from file URL", () => { | ||
| const fileUrl = pathToFileURL(__filename).href; | ||
| const result = findPackageJSON(fileUrl); | ||
|
|
||
| expect(typeof result).toBe("string"); | ||
| expect(path.basename(result!)).toBe("package.json"); | ||
| expect(path.isAbsolute(result!)).toBe(true); | ||
| }); | ||
|
|
||
| test.concurrent("finds package.json from directory path", () => { | ||
| const dirUrl = pathToFileURL(import.meta.dir).href; | ||
| const result = findPackageJSON(dirUrl); | ||
|
|
||
| expect(typeof result).toBe("string"); | ||
| expect(path.basename(result!)).toBe("package.json"); | ||
| expect(path.isAbsolute(result!)).toBe(true); | ||
| }); | ||
|
|
||
| test.concurrent("finds package.json from nested file", () => { | ||
| const nestedPath = path.join(import.meta.dir, "../../.."); | ||
| const fileUrl = pathToFileURL(path.join(nestedPath, "some-file.js")).href; | ||
| const result = findPackageJSON(fileUrl); | ||
|
|
||
| expect(typeof result).toBe("string"); | ||
| expect(path.basename(result!)).toBe("package.json"); | ||
| expect(path.isAbsolute(result!)).toBe(true); | ||
| }); | ||
|
|
||
| test.concurrent("returns null when no package.json found", () => { | ||
| // Use a path that's unlikely to have a package.json | ||
| const rootPath = path.parse(import.meta.dir).root; | ||
| const deepPath = path.join(rootPath, "nonexistent", "deep", "path", "file.js"); | ||
| const fileUrl = pathToFileURL(deepPath).href; | ||
| const result = findPackageJSON(fileUrl); | ||
|
|
||
| // Should return null when not found | ||
| expect(result).toBeNull(); | ||
| }); | ||
|
|
||
| test.concurrent("works with absolute paths as file URLs", () => { | ||
| const absolutePath = path.resolve(import.meta.dir, "node-module-findPackageJSON.test.ts"); | ||
| const fileUrl = pathToFileURL(absolutePath).href; | ||
| const result = findPackageJSON(fileUrl); | ||
|
|
||
| expect(typeof result).toBe("string"); | ||
| expect(path.basename(result!)).toBe("package.json"); | ||
| expect(path.isAbsolute(result!)).toBe(true); | ||
| }); | ||
|
|
||
| test.concurrent("accepts absolute path string", () => { | ||
| const result = findPackageJSON(import.meta.dir); | ||
| expect(typeof result).toBe("string"); | ||
| expect(path.basename(result!)).toBe("package.json"); | ||
| expect(path.isAbsolute(result!)).toBe(true); | ||
| }); | ||
| }); | ||
|
Comment on lines
+6
to
+62
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider adding edge case tests (optional). The current test suite provides solid coverage of typical use cases. To further improve robustness, consider adding tests for:
These are optional enhancements and not blockers for this PR. 🤖 Prompt for AI Agents |
||
Uh oh!
There was an error while loading. Please reload this page.