mirror of
https://github.com/oven-sh/bun
synced 2026-02-17 06:12:08 +00:00
Compare commits
1 Commits
claude/fix
...
claude/add
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7d7d5b99d |
@@ -36,6 +36,7 @@ pub const dns = @import("./api/bun/dns.zig");
|
||||
pub const FFI = @import("./api/ffi.zig").FFI;
|
||||
pub const HTMLRewriter = @import("./api/html_rewriter.zig");
|
||||
pub const FileSystemRouter = @import("./api/filesystem_router.zig").FileSystemRouter;
|
||||
pub const Directory = @import("./api/Directory.zig");
|
||||
pub const Glob = @import("./api/glob.zig");
|
||||
pub const H2FrameParser = @import("./api/bun/h2_frame_parser.zig").H2FrameParser;
|
||||
pub const JSBundler = @import("./api/JSBundler.zig").JSBundler;
|
||||
|
||||
@@ -17,6 +17,7 @@ pub const BunObject = struct {
|
||||
pub const createParsedShellScript = toJSCallback(bun.shell.ParsedShellScript.createParsedShellScript);
|
||||
pub const createShellInterpreter = toJSCallback(bun.shell.Interpreter.createShellInterpreter);
|
||||
pub const deflateSync = toJSCallback(JSZlib.deflateSync);
|
||||
pub const dir = toJSCallback(Bun.constructDirectory);
|
||||
pub const file = toJSCallback(WebCore.Blob.constructBunFile);
|
||||
pub const gunzipSync = toJSCallback(JSZlib.gunzipSync);
|
||||
pub const gzipSync = toJSCallback(JSZlib.gzipSync);
|
||||
@@ -50,6 +51,7 @@ pub const BunObject = struct {
|
||||
// --- Lazy property callbacks ---
|
||||
pub const CryptoHasher = toJSLazyPropertyCallback(Crypto.CryptoHasher.getter);
|
||||
pub const CSRF = toJSLazyPropertyCallback(Bun.getCSRFObject);
|
||||
pub const Directory = toJSLazyPropertyCallback(Bun.getDirectoryConstructor);
|
||||
pub const FFI = toJSLazyPropertyCallback(Bun.FFIObject.getter);
|
||||
pub const FileSystemRouter = toJSLazyPropertyCallback(Bun.getFileSystemRouter);
|
||||
pub const Glob = toJSLazyPropertyCallback(Bun.getGlobConstructor);
|
||||
@@ -115,6 +117,7 @@ pub const BunObject = struct {
|
||||
// --- Lazy property callbacks ---
|
||||
@export(&BunObject.CryptoHasher, .{ .name = lazyPropertyCallbackName("CryptoHasher") });
|
||||
@export(&BunObject.CSRF, .{ .name = lazyPropertyCallbackName("CSRF") });
|
||||
@export(&BunObject.Directory, .{ .name = lazyPropertyCallbackName("Directory") });
|
||||
@export(&BunObject.FFI, .{ .name = lazyPropertyCallbackName("FFI") });
|
||||
@export(&BunObject.FileSystemRouter, .{ .name = lazyPropertyCallbackName("FileSystemRouter") });
|
||||
@export(&BunObject.MD4, .{ .name = lazyPropertyCallbackName("MD4") });
|
||||
@@ -153,6 +156,7 @@ pub const BunObject = struct {
|
||||
@export(&BunObject.createParsedShellScript, .{ .name = callbackName("createParsedShellScript") });
|
||||
@export(&BunObject.createShellInterpreter, .{ .name = callbackName("createShellInterpreter") });
|
||||
@export(&BunObject.deflateSync, .{ .name = callbackName("deflateSync") });
|
||||
@export(&BunObject.dir, .{ .name = callbackName("dir") });
|
||||
@export(&BunObject.file, .{ .name = callbackName("file") });
|
||||
@export(&BunObject.gunzipSync, .{ .name = callbackName("gunzipSync") });
|
||||
@export(&BunObject.gzipSync, .{ .name = callbackName("gzipSync") });
|
||||
@@ -1269,6 +1273,16 @@ pub fn getGlobConstructor(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc
|
||||
return jsc.API.Glob.js.getConstructor(globalThis);
|
||||
}
|
||||
|
||||
pub fn getDirectoryConstructor(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
|
||||
return api.Directory.js.getConstructor(globalThis);
|
||||
}
|
||||
|
||||
/// Bun.dir(path) - creates a lazy Directory handle
|
||||
pub fn constructDirectory(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
||||
const dir = try api.Directory.constructor(globalObject, callframe);
|
||||
return dir.toJS(globalObject);
|
||||
}
|
||||
|
||||
pub fn getS3ClientConstructor(globalThis: *jsc.JSGlobalObject, _: *jsc.JSObject) jsc.JSValue {
|
||||
return jsc.WebCore.S3Client.js.getConstructor(globalThis);
|
||||
}
|
||||
|
||||
30
src/bun.js/api/Directory.classes.ts
Normal file
30
src/bun.js/api/Directory.classes.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { define } from "../../codegen/class-definitions";
|
||||
|
||||
export default [
|
||||
define({
|
||||
name: "Directory",
|
||||
construct: true,
|
||||
finalize: true,
|
||||
configurable: false,
|
||||
klass: {},
|
||||
JSType: "0b11101110",
|
||||
proto: {
|
||||
path: {
|
||||
getter: "getPath",
|
||||
cache: true,
|
||||
},
|
||||
name: {
|
||||
getter: "getName",
|
||||
cache: true,
|
||||
},
|
||||
files: {
|
||||
fn: "files",
|
||||
length: 0,
|
||||
},
|
||||
filesSync: {
|
||||
fn: "filesSync",
|
||||
length: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
298
src/bun.js/api/Directory.zig
Normal file
298
src/bun.js/api/Directory.zig
Normal file
@@ -0,0 +1,298 @@
|
||||
const Directory = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("bun");
|
||||
const jsc = bun.jsc;
|
||||
const strings = bun.strings;
|
||||
const Environment = bun.Environment;
|
||||
|
||||
pub const js = jsc.Codegen.JSDirectory;
|
||||
pub const toJS = js.toJS;
|
||||
pub const fromJS = js.fromJS;
|
||||
pub const fromJSDirect = js.fromJSDirect;
|
||||
|
||||
const DirIterator = bun.DirIterator;
|
||||
const Dirent = bun.jsc.Node.Dirent;
|
||||
|
||||
/// The path to the directory. This is stored as a string and the directory
|
||||
/// is NOT opened until files() or filesSync() is called - this is the lazy
|
||||
/// loading pattern similar to Bun.file().
|
||||
path: bun.String,
|
||||
|
||||
/// Construct a Directory from JavaScript arguments.
|
||||
/// Called when: `new Bun.Directory(path)` or `Bun.dir(path)`
|
||||
pub fn constructor(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!*Directory {
|
||||
const alloc = bun.default_allocator;
|
||||
const arguments = callframe.arguments_old(1).slice();
|
||||
|
||||
if (arguments.len == 0) {
|
||||
return globalObject.throwInvalidArguments("Expected directory path string", .{});
|
||||
}
|
||||
|
||||
const path_arg = arguments[0];
|
||||
if (!path_arg.isString()) {
|
||||
return globalObject.throwInvalidArgumentTypeValue("path", "string", path_arg);
|
||||
}
|
||||
|
||||
var path_string = try bun.String.fromJS(path_arg, globalObject);
|
||||
|
||||
if (path_string.isEmpty()) {
|
||||
return globalObject.throwInvalidArguments("Path cannot be empty", .{});
|
||||
}
|
||||
|
||||
// Store the path - we don't open the directory yet (lazy loading)
|
||||
const dir = bun.handleOom(alloc.create(Directory));
|
||||
dir.* = .{ .path = path_string };
|
||||
return dir;
|
||||
}
|
||||
|
||||
/// Called when the object is garbage collected
|
||||
pub fn finalize(this: *Directory) callconv(.c) void {
|
||||
const alloc = bun.default_allocator;
|
||||
this.path.deref();
|
||||
alloc.destroy(this);
|
||||
}
|
||||
|
||||
/// Get the path property
|
||||
pub fn getPath(this: *Directory, globalObject: *jsc.JSGlobalObject) jsc.JSValue {
|
||||
return this.path.toJS(globalObject);
|
||||
}
|
||||
|
||||
/// Get the name property (basename of the directory)
|
||||
pub fn getName(this: *Directory, globalObject: *jsc.JSGlobalObject) jsc.JSValue {
|
||||
const path_slice = this.path.toUTF8(bun.default_allocator);
|
||||
defer path_slice.deinit();
|
||||
|
||||
const basename = std.fs.path.basename(path_slice.slice());
|
||||
return bun.String.cloneUTF8(basename).toJS(globalObject);
|
||||
}
|
||||
|
||||
/// Async version of files() - returns Promise<Dirent[]>
|
||||
pub fn files(this: *Directory, globalObject: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
||||
const vm = globalObject.bunVM();
|
||||
|
||||
// Create a strong reference to the path for the async task
|
||||
this.path.ref();
|
||||
|
||||
var task = FilesTask.new(.{
|
||||
.path = this.path,
|
||||
.vm = vm,
|
||||
});
|
||||
|
||||
task.promise = jsc.JSPromise.Strong.init(globalObject);
|
||||
task.any_task = jsc.AnyTask.New(FilesTask, &FilesTask.runFromJS).init(task);
|
||||
task.ref.ref(vm);
|
||||
jsc.WorkPool.schedule(&task.task);
|
||||
|
||||
return task.promise.value();
|
||||
}
|
||||
|
||||
/// Sync version of files() - returns Dirent[]
|
||||
pub fn filesSync(this: *Directory, globalObject: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!jsc.JSValue {
|
||||
const path_slice = this.path.toUTF8(bun.default_allocator);
|
||||
defer path_slice.deinit();
|
||||
|
||||
return readDirectoryEntries(globalObject, path_slice.slice());
|
||||
}
|
||||
|
||||
/// Read directory entries synchronously and return as JS array of Dirent objects
|
||||
fn readDirectoryEntries(globalObject: *jsc.JSGlobalObject, path: []const u8) bun.JSError!jsc.JSValue {
|
||||
const path_z = bun.default_allocator.dupeZ(u8, path) catch return globalObject.throw("Out of memory", .{});
|
||||
defer bun.default_allocator.free(path_z);
|
||||
|
||||
// Open the directory
|
||||
const flags = bun.O.DIRECTORY | bun.O.RDONLY;
|
||||
const fd = switch (bun.sys.open(path_z, flags, 0)) {
|
||||
.err => |err| {
|
||||
const js_err = err.withPath(path).toJS(globalObject);
|
||||
return globalObject.throwValue(js_err);
|
||||
},
|
||||
.result => |fd_result| fd_result,
|
||||
};
|
||||
defer fd.close();
|
||||
|
||||
// Use the directory iterator
|
||||
var iterator = DirIterator.iterate(fd, .u8);
|
||||
|
||||
// Collect entries
|
||||
var entries = std.ArrayListUnmanaged(Dirent){};
|
||||
defer {
|
||||
for (entries.items) |*item| {
|
||||
item.deref();
|
||||
}
|
||||
entries.deinit(bun.default_allocator);
|
||||
}
|
||||
|
||||
var dirent_path = bun.String.cloneUTF8(path);
|
||||
defer dirent_path.deref();
|
||||
|
||||
var entry = iterator.next();
|
||||
while (switch (entry) {
|
||||
.err => |err| {
|
||||
const js_err = err.withPath(path).toJS(globalObject);
|
||||
return globalObject.throwValue(js_err);
|
||||
},
|
||||
.result => |ent| ent,
|
||||
}) |current| : (entry = iterator.next()) {
|
||||
const utf8_name = current.name.slice();
|
||||
dirent_path.ref();
|
||||
entries.append(bun.default_allocator, .{
|
||||
.name = bun.String.cloneUTF8(utf8_name),
|
||||
.path = dirent_path,
|
||||
.kind = current.kind,
|
||||
}) catch return globalObject.throw("Out of memory", .{});
|
||||
}
|
||||
|
||||
// Convert to JS array
|
||||
var array = try jsc.JSValue.createEmptyArray(globalObject, entries.items.len);
|
||||
var previous_jsstring: ?*jsc.JSString = null;
|
||||
|
||||
for (entries.items, 0..) |*item, i| {
|
||||
const js_dirent = try item.toJS(globalObject, &previous_jsstring);
|
||||
try array.putIndex(globalObject, @truncate(i), js_dirent);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/// Async task for reading directory entries
|
||||
const FilesTask = struct {
|
||||
task: jsc.WorkPoolTask = .{ .callback = &workPoolCallback },
|
||||
promise: jsc.JSPromise.Strong = .{},
|
||||
vm: *jsc.VirtualMachine,
|
||||
path: bun.String,
|
||||
any_task: jsc.AnyTask = undefined,
|
||||
ref: bun.Async.KeepAlive = .{},
|
||||
result: Result = undefined,
|
||||
|
||||
const Result = union(enum) {
|
||||
success: []Dirent,
|
||||
err: bun.sys.Error,
|
||||
};
|
||||
|
||||
pub const new = bun.TrivialNew(@This());
|
||||
|
||||
fn workPoolCallback(task_ptr: *jsc.WorkPoolTask) void {
|
||||
const this: *FilesTask = @fieldParentPtr("task", task_ptr);
|
||||
defer this.vm.enqueueTaskConcurrent(jsc.ConcurrentTask.create(this.any_task.task()));
|
||||
|
||||
const path_slice = this.path.toUTF8(bun.default_allocator);
|
||||
defer path_slice.deinit();
|
||||
|
||||
const path_z = bun.default_allocator.dupeZ(u8, path_slice.slice()) catch {
|
||||
this.result = .{ .err = bun.sys.Error.fromCode(.NOMEM, .open) };
|
||||
return;
|
||||
};
|
||||
defer bun.default_allocator.free(path_z);
|
||||
|
||||
// Open the directory
|
||||
const flags = bun.O.DIRECTORY | bun.O.RDONLY;
|
||||
const fd = switch (bun.sys.open(path_z, flags, 0)) {
|
||||
.err => |err| {
|
||||
this.result = .{ .err = err };
|
||||
return;
|
||||
},
|
||||
.result => |fd_result| fd_result,
|
||||
};
|
||||
defer fd.close();
|
||||
|
||||
// Use the directory iterator
|
||||
var iterator = DirIterator.iterate(fd, .u8);
|
||||
|
||||
// Collect entries
|
||||
var entries = std.ArrayListUnmanaged(Dirent){};
|
||||
|
||||
var dirent_path = bun.String.cloneUTF8(path_slice.slice());
|
||||
defer dirent_path.deref();
|
||||
|
||||
var entry = iterator.next();
|
||||
while (switch (entry) {
|
||||
.err => |err| {
|
||||
for (entries.items) |*item| {
|
||||
item.deref();
|
||||
}
|
||||
entries.deinit(bun.default_allocator);
|
||||
this.result = .{ .err = err };
|
||||
return;
|
||||
},
|
||||
.result => |ent| ent,
|
||||
}) |current| : (entry = iterator.next()) {
|
||||
const utf8_name = current.name.slice();
|
||||
dirent_path.ref();
|
||||
entries.append(bun.default_allocator, .{
|
||||
.name = bun.String.cloneUTF8(utf8_name),
|
||||
.path = dirent_path,
|
||||
.kind = current.kind,
|
||||
}) catch {
|
||||
for (entries.items) |*item| {
|
||||
item.deref();
|
||||
}
|
||||
entries.deinit(bun.default_allocator);
|
||||
this.result = .{ .err = bun.sys.Error.fromCode(.NOMEM, .open) };
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
this.result = .{ .success = entries.toOwnedSlice(bun.default_allocator) catch &.{} };
|
||||
}
|
||||
|
||||
pub fn runFromJS(this: *FilesTask) bun.JSTerminated!void {
|
||||
defer this.deinit();
|
||||
|
||||
if (this.vm.isShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const globalObject = this.vm.global;
|
||||
const promise = this.promise.swap();
|
||||
|
||||
switch (this.result) {
|
||||
.err => |err| {
|
||||
const js_err = err.toJS(globalObject);
|
||||
try promise.reject(globalObject, js_err);
|
||||
},
|
||||
.success => |entries| {
|
||||
defer bun.default_allocator.free(entries);
|
||||
|
||||
// Convert to JS array
|
||||
var array = jsc.JSValue.createEmptyArray(globalObject, entries.len) catch {
|
||||
for (entries) |*item| {
|
||||
@constCast(item).deref();
|
||||
}
|
||||
try promise.reject(globalObject, globalObject.createErrorInstance("Out of memory", .{}));
|
||||
return;
|
||||
};
|
||||
|
||||
var previous_jsstring: ?*jsc.JSString = null;
|
||||
for (entries, 0..) |*item, i| {
|
||||
const js_dirent = @constCast(item).toJS(globalObject, &previous_jsstring) catch {
|
||||
for (entries[i..]) |*remaining| {
|
||||
@constCast(remaining).deref();
|
||||
}
|
||||
// An exception is already pending from toJS, so we just return
|
||||
return error.JSTerminated;
|
||||
};
|
||||
array.putIndex(globalObject, @truncate(i), js_dirent) catch {
|
||||
for (entries[i + 1 ..]) |*remaining| {
|
||||
@constCast(remaining).deref();
|
||||
}
|
||||
return error.JSTerminated;
|
||||
};
|
||||
}
|
||||
|
||||
try promise.resolve(globalObject, array);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn deinit(this: *FilesTask) void {
|
||||
this.ref.unref(this.vm);
|
||||
this.path.deref();
|
||||
this.promise.deinit();
|
||||
bun.destroy(this);
|
||||
}
|
||||
};
|
||||
|
||||
comptime {
|
||||
_ = js;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#define FOR_EACH_GETTER(macro) \
|
||||
macro(CSRF) \
|
||||
macro(CryptoHasher) \
|
||||
macro(Directory) \
|
||||
macro(FFI) \
|
||||
macro(FileSystemRouter) \
|
||||
macro(Glob) \
|
||||
@@ -44,6 +45,7 @@
|
||||
macro(createParsedShellScript) \
|
||||
macro(createShellInterpreter) \
|
||||
macro(deflateSync) \
|
||||
macro(dir) \
|
||||
macro(file) \
|
||||
macro(fs) \
|
||||
macro(gc) \
|
||||
|
||||
@@ -742,6 +742,8 @@ JSC_DEFINE_HOST_FUNCTION(functionFileURLToPath, (JSC::JSGlobalObject * globalObj
|
||||
deepEquals functionBunDeepEquals DontDelete|Function 2
|
||||
deepMatch functionBunDeepMatch DontDelete|Function 2
|
||||
deflateSync BunObject_callback_deflateSync DontDelete|Function 1
|
||||
dir BunObject_callback_dir DontDelete|Function 1
|
||||
Directory BunObject_lazyPropCb_wrap_Directory DontDelete|PropertyCallback
|
||||
dns constructDNSObject ReadOnly|DontDelete|PropertyCallback
|
||||
enableANSIColors BunObject_lazyPropCb_wrap_enableANSIColors DontDelete|PropertyCallback
|
||||
env constructEnvObject ReadOnly|DontDelete|PropertyCallback
|
||||
|
||||
@@ -24,6 +24,7 @@ pub const Classes = struct {
|
||||
pub const ExpectTypeOf = jsc.Expect.ExpectTypeOf;
|
||||
pub const ScopeFunctions = jsc.Jest.bun_test.ScopeFunctions;
|
||||
pub const DoneCallback = jsc.Jest.bun_test.DoneCallback;
|
||||
pub const Directory = api.Directory;
|
||||
pub const FileSystemRouter = api.FileSystemRouter;
|
||||
pub const Glob = api.Glob;
|
||||
pub const ShellInterpreter = api.Shell.Interpreter;
|
||||
|
||||
162
test/js/bun/bun-dir.test.ts
Normal file
162
test/js/bun/bun-dir.test.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { tempDir } from "harness";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
describe("Bun.dir()", () => {
|
||||
test("creates a lazy Directory object", () => {
|
||||
const dir = Bun.dir("/tmp");
|
||||
expect(dir).toBeDefined();
|
||||
expect(dir).toBeInstanceOf(Bun.Directory);
|
||||
});
|
||||
|
||||
test("has path property", () => {
|
||||
const dir = Bun.dir("/tmp");
|
||||
expect(dir.path).toBe("/tmp");
|
||||
});
|
||||
|
||||
test("has name property (basename)", () => {
|
||||
const dir = Bun.dir("/some/nested/folder");
|
||||
expect(dir.name).toBe("folder");
|
||||
});
|
||||
|
||||
test("is lazy - doesn't open directory until files() is called", () => {
|
||||
// This should NOT throw even though the path doesn't exist
|
||||
const dir = Bun.dir("/this/path/does/not/exist");
|
||||
expect(dir.path).toBe("/this/path/does/not/exist");
|
||||
expect(dir.name).toBe("exist");
|
||||
});
|
||||
|
||||
test("throws when path is not a string", () => {
|
||||
// @ts-expect-error - intentionally passing wrong type
|
||||
expect(() => Bun.dir(123)).toThrow();
|
||||
// @ts-expect-error - intentionally passing wrong type
|
||||
expect(() => Bun.dir(null)).toThrow();
|
||||
});
|
||||
|
||||
test("throws when path is empty", () => {
|
||||
expect(() => Bun.dir("")).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bun.Directory constructor", () => {
|
||||
test("can be constructed with new", () => {
|
||||
const dir = new Bun.Directory("/tmp");
|
||||
expect(dir).toBeInstanceOf(Bun.Directory);
|
||||
expect(dir.path).toBe("/tmp");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Directory.filesSync()", () => {
|
||||
test("returns array of Dirent objects", () => {
|
||||
using dir_path = tempDir("bun-dir-test", {
|
||||
"file1.txt": "hello",
|
||||
"file2.txt": "world",
|
||||
});
|
||||
|
||||
// Create subdirectory manually
|
||||
fs.mkdirSync(path.join(String(dir_path), "subdir"), { recursive: true });
|
||||
|
||||
const dir = Bun.dir(String(dir_path));
|
||||
const entries = dir.filesSync();
|
||||
|
||||
expect(Array.isArray(entries)).toBe(true);
|
||||
expect(entries.length).toBe(3);
|
||||
|
||||
// Check that entries are Dirent-like objects
|
||||
for (const entry of entries) {
|
||||
expect(typeof entry.name).toBe("string");
|
||||
expect(typeof entry.isFile).toBe("function");
|
||||
expect(typeof entry.isDirectory).toBe("function");
|
||||
}
|
||||
|
||||
// Check specific entries
|
||||
const names = entries.map(e => e.name).sort();
|
||||
expect(names).toEqual(["file1.txt", "file2.txt", "subdir"]);
|
||||
|
||||
// Check file types
|
||||
const file1 = entries.find(e => e.name === "file1.txt");
|
||||
expect(file1?.isFile()).toBe(true);
|
||||
expect(file1?.isDirectory()).toBe(false);
|
||||
|
||||
const subdir = entries.find(e => e.name === "subdir");
|
||||
expect(subdir?.isDirectory()).toBe(true);
|
||||
expect(subdir?.isFile()).toBe(false);
|
||||
});
|
||||
|
||||
test("throws for non-existent directory", () => {
|
||||
const dir = Bun.dir("/this/path/definitely/does/not/exist");
|
||||
expect(() => dir.filesSync()).toThrow();
|
||||
});
|
||||
|
||||
test("returns empty array for empty directory", () => {
|
||||
using dir_path = tempDir("bun-dir-empty-test", {});
|
||||
|
||||
const dir = Bun.dir(String(dir_path));
|
||||
const entries = dir.filesSync();
|
||||
|
||||
expect(Array.isArray(entries)).toBe(true);
|
||||
expect(entries.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Directory.files()", () => {
|
||||
test("returns Promise that resolves to array of Dirent objects", async () => {
|
||||
using dir_path = tempDir("bun-dir-async-test", {
|
||||
"async1.txt": "async content 1",
|
||||
"async2.txt": "async content 2",
|
||||
});
|
||||
|
||||
const dir = Bun.dir(String(dir_path));
|
||||
const promise = dir.files();
|
||||
|
||||
expect(promise).toBeInstanceOf(Promise);
|
||||
|
||||
const entries = await promise;
|
||||
|
||||
expect(Array.isArray(entries)).toBe(true);
|
||||
expect(entries.length).toBe(2);
|
||||
|
||||
const names = entries.map(e => e.name).sort();
|
||||
expect(names).toEqual(["async1.txt", "async2.txt"]);
|
||||
});
|
||||
|
||||
test("rejects for non-existent directory", async () => {
|
||||
const dir = Bun.dir("/this/path/definitely/does/not/exist/async");
|
||||
await expect(dir.files()).rejects.toThrow();
|
||||
});
|
||||
|
||||
test("can be called multiple times on same Directory", async () => {
|
||||
using dir_path = tempDir("bun-dir-multi-test", {
|
||||
"multi.txt": "content",
|
||||
});
|
||||
|
||||
const dir = Bun.dir(String(dir_path));
|
||||
|
||||
// Call files() multiple times
|
||||
const [entries1, entries2] = await Promise.all([dir.files(), dir.files()]);
|
||||
|
||||
expect(entries1.length).toBe(1);
|
||||
expect(entries2.length).toBe(1);
|
||||
expect(entries1[0].name).toBe("multi.txt");
|
||||
expect(entries2[0].name).toBe("multi.txt");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Directory Dirent properties", () => {
|
||||
test("Dirent has parentPath/path property", () => {
|
||||
using dir_path = tempDir("bun-dir-parent-test", {
|
||||
"test.txt": "test",
|
||||
});
|
||||
|
||||
const dir = Bun.dir(String(dir_path));
|
||||
const entries = dir.filesSync();
|
||||
|
||||
expect(entries.length).toBe(1);
|
||||
const entry = entries[0];
|
||||
|
||||
// Check path/parentPath
|
||||
expect(entry.path).toBe(String(dir_path));
|
||||
expect(entry.parentPath).toBe(String(dir_path));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user