Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/write-files-string-content.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vercel/sandbox": patch
---

Accept `string` and `Uint8Array` in `writeFiles()` content, not just `Buffer`.
6 changes: 5 additions & 1 deletion packages/vercel-sandbox/src/api-client/api-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,11 @@ export class APIClient extends BaseClient {
async writeFiles(params: {
sandboxId: string;
cwd: string;
files: { path: string; content: Buffer; mode?: number }[];
files: {
path: string;
content: string | Uint8Array;
mode?: number;
}[];
extractDir: string;
signal?: AbortSignal;
}) {
Expand Down
68 changes: 64 additions & 4 deletions packages/vercel-sandbox/src/api-client/file-writer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ describe("FileWriter", () => {
name: "hello.txt",
content: Buffer.from("Hello world"),
});
writer.end();
const end = writer.end();

const files = await extractFiles(writer.readable);
await end;
expect(files.get("hello.txt")?.toString()).toBe("Hello world");
});

Expand All @@ -42,9 +43,10 @@ describe("FileWriter", () => {
name: "utf8.txt",
content: Buffer.from(content),
});
writer.end();
const end = writer.end();

const files = await extractFiles(writer.readable);
await end;
expect(files.get("utf8.txt")?.toString()).toBe(content);
});

Expand All @@ -58,14 +60,71 @@ describe("FileWriter", () => {
name: "b.txt",
content: Buffer.from("file b"),
});
writer.end();
const end = writer.end();

const files = await extractFiles(writer.readable);
await end;
expect(files.size).toBe(2);
expect(files.get("a.txt")?.toString()).toBe("file a");
expect(files.get("b.txt")?.toString()).toBe("file b");
});

it("writes string content", async () => {
const writer = new FileWriter();
await writer.addFile({
name: "hello.txt",
content: "Hello world",
});
const end = writer.end();

const files = await extractFiles(writer.readable);
await end;
expect(files.get("hello.txt")?.toString()).toBe("Hello world");
});

it("writes multi-byte UTF-8 string content", async () => {
const writer = new FileWriter();
const content = "café ☕ — Grüße aus München 🌍 日本語テスト";
await writer.addFile({
name: "utf8.txt",
content,
});
const end = writer.end();

const files = await extractFiles(writer.readable);
await end;
expect(files.get("utf8.txt")?.toString()).toBe(content);
});

it("writes Uint8Array content", async () => {
const writer = new FileWriter();
const content = new TextEncoder().encode("Hello world");
await writer.addFile({
name: "hello.txt",
content,
});
const end = writer.end();

const files = await extractFiles(writer.readable);
await end;
expect(files.get("hello.txt")?.toString()).toBe("Hello world");
});

it("writes multi-byte UTF-8 Uint8Array content", async () => {
const writer = new FileWriter();
const text = "café ☕ — Grüße aus München 🌍 日本語テスト";
const content = new TextEncoder().encode(text);
await writer.addFile({
name: "utf8.txt",
content,
});
const end = writer.end();

const files = await extractFiles(writer.readable);
await end;
expect(files.get("utf8.txt")?.toString()).toBe(text);
});

it("writes stream content with explicit size", async () => {
const content = Buffer.from("streamed content");
const writer = new FileWriter();
Expand All @@ -74,9 +133,10 @@ describe("FileWriter", () => {
content: Readable.from(content),
size: content.length,
});
writer.end();
const end = writer.end();

const files = await extractFiles(writer.readable);
await end;
expect(files.get("stream.txt")?.toString()).toBe("streamed content");
});
});
14 changes: 9 additions & 5 deletions packages/vercel-sandbox/src/api-client/file-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import zlib from "zlib";
import tar, { type Pack } from "tar-stream";
import { Readable } from "stream";

interface FileBuffer {
interface FileData {
/**
* The name (path) of the file to write.
*/
name: string;
/**
* The content of the file as a Buffer.
* The content of the file.
*/
content: Buffer;
content: string | Uint8Array;
Comment thread
TooTallNate marked this conversation as resolved.
/**
* The file mode (permissions) to set on the file.
* For example, 0o755 for executable files.
Expand Down Expand Up @@ -62,12 +62,16 @@ export class FileWriter {
* Returns a Promise resolved once the file is written in the
* stream.
*/
async addFile(file: FileBuffer | FileStream) {
async addFile(file: FileData | FileStream) {
return new Promise<void>((resolve, reject) => {
const entry = this.pack.entry(
"size" in file
? { name: file.name, size: file.size, mode: file.mode }
: { name: file.name, size: Buffer.byteLength(file.content), mode: file.mode },
: {
name: file.name,
size: Buffer.byteLength(file.content),
Comment thread
marc-vercel marked this conversation as resolved.
mode: file.mode,
},
(error) => {
if (error) {
return reject(error);
Expand Down
8 changes: 6 additions & 2 deletions packages/vercel-sandbox/src/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,11 +703,15 @@ export class Sandbox {
* @example
* // Write an executable script
* await sandbox.writeFiles([
* { path: "/usr/local/bin/myscript", content: Buffer.from("#!/bin/bash\necho hello"), mode: 0o755 }
* { path: "/usr/local/bin/myscript", content: "#!/bin/bash\necho hello", mode: 0o755 }
* ]);
*/
async writeFiles(
files: { path: string; content: Buffer; mode?: number }[],
files: {
path: string;
content: string | Uint8Array;
mode?: number;
}[],
opts?: { signal?: AbortSignal },
) {
"use step";
Expand Down
Loading