Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions src/bun.js/node/node_fs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,37 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
const src = args.src.osPath(&src_buf);
const dest = args.dest.osPath(&dest_buf);

// Check if source is an embedded file in a standalone executable
if (bun.StandaloneModuleGraph.get()) |graph| {
const src_slice = if (Environment.isWindows) brk: {
var path_buf: bun.PathBuffer = undefined;
break :brk bun.strings.fromWPath(&path_buf, src);
} else src;
if (graph.find(src_slice)) |file| {
// Embedded files cannot be directories, so copy directly
// fs.cp creates parent directories if they don't exist
const copy_mode: constants.Copyfile = @enumFromInt(if (args.flags.errorOnExist or !args.flags.force) constants.COPYFILE_EXCL else @as(u8, 0));
const r = NodeFS.copyEmbeddedFileToDestination(dest, file.contents, copy_mode, true);
if (r == .err) {
if (r.err.errno == @intFromEnum(E.EXIST) and !args.flags.errorOnExist) {
// File exists but errorOnExist is false - treat as no-op
this.finishConcurrently(.success);
return;
}
this.finishConcurrently(.{ .err = .{
.errno = r.err.errno,
.syscall = .copyfile,
.path = nodefs.osPathIntoSyncErrorBuf(src),
.dest = nodefs.osPathIntoSyncErrorBuf(dest),
} });
return;
}
this.onCopy(src, dest);
this.finishConcurrently(r);
return;
}
}

if (Environment.isWindows) {
const attributes = c.GetFileAttributesW(src);
if (attributes == c.INVALID_FILE_ATTRIBUTES) {
Expand Down Expand Up @@ -3498,6 +3529,72 @@ pub const NodeFS = struct {
return .success;
}

/// Copy embedded file contents to a destination path.
/// Used for copying files from standalone executables.
/// If `create_parents` is true, creates parent directories if they don't exist (for fs.cp).
/// If `create_parents` is false, fails with ENOENT if parent doesn't exist (for fs.copyFile).
fn copyEmbeddedFileToDestination(
dest: bun.OSPathSliceZ,
contents: []const u8,
mode: constants.Copyfile,
comptime create_parents: bool,
) Maybe(Return.CopyFile) {
const ret = Maybe(Return.CopyFile);
var flags: i32 = bun.O.CREAT | bun.O.WRONLY | bun.O.TRUNC;
if (mode.shouldntOverwrite()) {
flags |= bun.O.EXCL;
}

const dest_fd = dest_fd: {
switch (Syscall.openatOSPath(bun.FD.cwd(), dest, flags, default_permission)) {
.result => |result| break :dest_fd result,
.err => |err| {
if (create_parents and err.getErrno() == .NOENT) {
// Create the parent directory if it doesn't exist
// Uses the same pattern as _copySingleFileSync's fallback path
if (Environment.isWindows) {
bun.makePathW(std.fs.cwd(), bun.path.dirnameW(dest)) catch {};
} else {
bun.makePath(std.fs.cwd(), bun.path.dirname(dest, .posix)) catch {};
}
// Retry opening the file - if mkdir failed, this will return the error
switch (Syscall.openatOSPath(bun.FD.cwd(), dest, flags, default_permission)) {
.result => |result| break :dest_fd result,
.err => {},
}
}
return .{ .err = err };
},
}
};
defer dest_fd.close();

var buf = contents;
var written: usize = 0;

while (buf.len > 0) {
switch (bun.sys.write(dest_fd, buf)) {
.err => |err| return .{ .err = err },
.result => |amt| {
buf = buf[amt..];
written += amt;
if (amt == 0) {
break;
}
},
}
}

// Truncate to exact size written
if (Environment.isWindows) {
_ = bun.windows.SetEndOfFile(dest_fd.cast());
} else {
_ = Syscall.ftruncate(dest_fd, @intCast(@as(u63, @truncate(written))));
}

return ret.success;
}

pub fn copyFile(this: *NodeFS, args: Arguments.CopyFile, _: Flavor) Maybe(Return.CopyFile) {
return switch (this.copyFileInner(args)) {
.result => .success,
Expand All @@ -3517,6 +3614,23 @@ pub const NodeFS = struct {
fn copyFileInner(fs: *NodeFS, args: Arguments.CopyFile) Maybe(Return.CopyFile) {
const ret = Maybe(Return.CopyFile);

// Check if source is an embedded file in a standalone executable
if (bun.StandaloneModuleGraph.get()) |graph| {
if (graph.find(args.src.slice())) |file| {
// Cannot clone an embedded file
if (args.mode.isForceClone()) {
return Maybe(Return.CopyFile){ .err = .{
.errno = @intFromEnum(SystemErrno.ENOTSUP),
.syscall = .copyfile,
} };
}
var dest_buf: bun.OSPathBuffer = undefined;
const dest = args.dest.osPath(&dest_buf);
// fs.copyFile does NOT create parent directories (matches Node.js behavior)
return copyEmbeddedFileToDestination(dest, file.contents, args.mode, false);
}
}

// TODO: do we need to fchown?
if (comptime Environment.isMac) {
var src_buf: bun.PathBuffer = undefined;
Expand Down Expand Up @@ -6014,6 +6128,32 @@ pub const NodeFS = struct {
const src = src_buf[0..src_dir_len :0];
const dest = dest_buf[0..dest_dir_len :0];

// Check if source is an embedded file in a standalone executable
if (bun.StandaloneModuleGraph.get()) |graph| {
const src_slice = if (Environment.isWindows) brk: {
var path_buf: bun.PathBuffer = undefined;
break :brk bun.strings.fromWPath(&path_buf, src);
} else src;
if (graph.find(src_slice)) |file| {
// Embedded files cannot be directories, so copy directly
// fs.cp creates parent directories if they don't exist
const copy_mode: constants.Copyfile = @enumFromInt(if (cp_flags.errorOnExist or !cp_flags.force) constants.COPYFILE_EXCL else @as(u8, 0));
const r = copyEmbeddedFileToDestination(dest, file.contents, copy_mode, true);
if (r == .err) {
if (r.err.errno == @intFromEnum(E.EXIST) and !cp_flags.errorOnExist) {
return .success;
}
return .{ .err = .{
.errno = r.err.errno,
.syscall = .copyfile,
.path = this.osPathIntoSyncErrorBuf(src),
.dest = this.osPathIntoSyncErrorBuf(dest),
} };
}
return r;
}
}

if (Environment.isWindows) {
const attributes = c.GetFileAttributesW(src);
if (attributes == c.INVALID_FILE_ATTRIBUTES) {
Expand Down
160 changes: 160 additions & 0 deletions test/bundler/bundler_compile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -735,4 +735,164 @@ const server = serve({
.env(bunEnv)
.throws(true);
});

itBundled("compile/CopyFileFromEmbeddedFile", {
compile: true,
assetNaming: "[name].[ext]",
files: {
"/entry.ts": /* js */ `
import { copyFileSync, readFileSync, rmSync, existsSync } from 'fs';
import embeddedPath from './data.txt' with { type: 'file' };

// Remove data.txt from filesystem to verify we're reading from the embedded bundle
rmSync('./data.txt', { force: true });

// Copy embedded file to a new location
const destPath = './copied-data.txt';
copyFileSync(embeddedPath, destPath);

// Verify the copy worked
if (!existsSync(destPath)) throw new Error('Copy failed: destination does not exist');

const content = readFileSync(destPath, 'utf8');
if (content.trim() !== 'Hello from embedded file!') {
throw new Error('Copy failed: content mismatch - got: ' + content);
}

console.log('fs.copyFile from embedded file: OK');
`,
"/data.txt": "Hello from embedded file!",
},
run: { stdout: "fs.copyFile from embedded file: OK", setCwd: true },
});

itBundled("compile/CopyFileAsyncFromEmbeddedFile", {
compile: true,
assetNaming: "[name].[ext]",
files: {
"/entry.ts": /* js */ `
import { copyFile, readFile, rm, access } from 'fs/promises';
import embeddedPath from './data-async.txt' with { type: 'file' };

// Remove data-async.txt from filesystem to verify we're reading from the embedded bundle
await rm('./data-async.txt', { force: true });

// Copy embedded file to a new location using async API
const destPath = './copied-data-async.txt';
await copyFile(embeddedPath, destPath);

// Verify the copy worked using async APIs
try {
await access(destPath);
} catch {
throw new Error('Copy failed: destination does not exist');
}

const content = await readFile(destPath, 'utf8');
if (content.trim() !== 'Async embedded content!') {
throw new Error('Copy failed: content mismatch - got: ' + content);
}

console.log('fs.copyFile async from embedded file: OK');
`,
"/data-async.txt": "Async embedded content!",
},
run: { stdout: "fs.copyFile async from embedded file: OK", setCwd: true },
});

itBundled("compile/CpFromEmbeddedFile", {
compile: true,
assetNaming: "[name].[ext]",
files: {
"/entry.ts": /* js */ `
import { cpSync, readFileSync, rmSync, existsSync } from 'fs';
import embeddedPath from './source.dat' with { type: 'file' };

// Remove source.dat from filesystem to verify we're reading from the embedded bundle
rmSync('./source.dat', { force: true });

// Copy embedded file using fs.cp (single file mode)
const destPath = './dest.dat';
cpSync(embeddedPath, destPath);

// Verify the copy worked
if (!existsSync(destPath)) throw new Error('cp failed: destination does not exist');

const content = readFileSync(destPath, 'utf8');
if (content.trim() !== 'Data from cp test') {
throw new Error('cp failed: content mismatch - got: ' + content);
}

console.log('fs.cp from embedded file: OK');
`,
"/source.dat": "Data from cp test",
},
run: { stdout: "fs.cp from embedded file: OK", setCwd: true },
});

itBundled("compile/CpAsyncFromEmbeddedFile", {
compile: true,
assetNaming: "[name].[ext]",
files: {
"/entry.ts": /* js */ `
import { cp, rm, readFile, access } from 'fs/promises';
import embeddedPath from './async-source.dat' with { type: 'file' };

// Remove source file from filesystem to verify we're reading from the embedded bundle
await rm('./async-source.dat', { force: true });

// Copy embedded file using async fs.cp (single file mode)
const destPath = './async-dest.dat';
await cp(embeddedPath, destPath);

// Verify the copy worked using async APIs
try {
await access(destPath);
} catch {
throw new Error('async cp failed: destination does not exist');
}

const content = await readFile(destPath, 'utf8');
if (content.trim() !== 'Async cp test data') {
throw new Error('async cp failed: content mismatch - got: ' + content);
}

console.log('fs.cp async from embedded file: OK');
`,
"/async-source.dat": "Async cp test data",
},
run: { stdout: "fs.cp async from embedded file: OK", setCwd: true },
});

itBundled("compile/CpFromEmbeddedFileToSubdir", {
compile: true,
assetNaming: "[name].[ext]",
files: {
"/entry.ts": /* js */ `
import { cpSync, readFileSync, rmSync, existsSync } from 'fs';
import embeddedPath from './subdir-test.txt' with { type: 'file' };

// Remove source file and any existing subdir from filesystem
rmSync('./subdir-test.txt', { force: true });
rmSync('./newdir', { recursive: true, force: true });

// Copy embedded file to a nested subdirectory that doesn't exist
// fs.cp should create parent directories automatically
const destPath = './newdir/nested/copied.txt';
cpSync(embeddedPath, destPath);

// Verify the copy worked
if (!existsSync(destPath)) throw new Error('cp failed: destination does not exist');

const content = readFileSync(destPath, 'utf8');
if (content.trim() !== 'Subdir test content') {
throw new Error('cp failed: content mismatch - got: ' + content);
}

console.log('fs.cp to subdir from embedded file: OK');
`,
"/subdir-test.txt": "Subdir test content",
},
run: { stdout: "fs.cp to subdir from embedded file: OK", setCwd: true },
});
});