Skip to content

Commit 33c9081

Browse files
TooTallNatemarc-vercel
authored andcommitted
feat(sandbox): accept string and Uint8Array in writeFiles content (#128)
## Summary - Widens the `writeFiles()` `content` type from `Buffer` to `string | Uint8Array` (`Buffer` is implied since it extends `Uint8Array`) - This allows passing strings directly without needing `Buffer.from()`, which is important for environments where `Buffer` is not available (e.g. workflow runtime contexts) - Renames the internal `FileBuffer` interface to `FileData` to reflect the widened type - Adds tests for string and Uint8Array content in `FileWriter` - Fixes all `FileWriter` tests to properly `await` the `writer.end()` promise
1 parent 1cdb622 commit 33c9081

5 files changed

Lines changed: 89 additions & 12 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@vercel/sandbox": patch
3+
---
4+
5+
Accept `string` and `Uint8Array` in `writeFiles()` content, not just `Buffer`.

packages/vercel-sandbox/src/api-client/api-client.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,11 @@ export class APIClient extends BaseClient {
463463
async writeFiles(params: {
464464
sessionId: string;
465465
cwd: string;
466-
files: { path: string; content: Buffer; mode?: number }[];
466+
files: {
467+
path: string;
468+
content: string | Uint8Array;
469+
mode?: number;
470+
}[];
467471
extractDir: string;
468472
signal?: AbortSignal;
469473
}) {

packages/vercel-sandbox/src/api-client/file-writer.test.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@ describe("FileWriter", () => {
2929
name: "hello.txt",
3030
content: Buffer.from("Hello world"),
3131
});
32-
writer.end();
32+
const end = writer.end();
3333

3434
const files = await extractFiles(writer.readable);
35+
await end;
3536
expect(files.get("hello.txt")?.toString()).toBe("Hello world");
3637
});
3738

@@ -42,9 +43,10 @@ describe("FileWriter", () => {
4243
name: "utf8.txt",
4344
content: Buffer.from(content),
4445
});
45-
writer.end();
46+
const end = writer.end();
4647

4748
const files = await extractFiles(writer.readable);
49+
await end;
4850
expect(files.get("utf8.txt")?.toString()).toBe(content);
4951
});
5052

@@ -58,14 +60,71 @@ describe("FileWriter", () => {
5860
name: "b.txt",
5961
content: Buffer.from("file b"),
6062
});
61-
writer.end();
63+
const end = writer.end();
6264

6365
const files = await extractFiles(writer.readable);
66+
await end;
6467
expect(files.size).toBe(2);
6568
expect(files.get("a.txt")?.toString()).toBe("file a");
6669
expect(files.get("b.txt")?.toString()).toBe("file b");
6770
});
6871

72+
it("writes string content", async () => {
73+
const writer = new FileWriter();
74+
await writer.addFile({
75+
name: "hello.txt",
76+
content: "Hello world",
77+
});
78+
const end = writer.end();
79+
80+
const files = await extractFiles(writer.readable);
81+
await end;
82+
expect(files.get("hello.txt")?.toString()).toBe("Hello world");
83+
});
84+
85+
it("writes multi-byte UTF-8 string content", async () => {
86+
const writer = new FileWriter();
87+
const content = "café ☕ — Grüße aus München 🌍 日本語テスト";
88+
await writer.addFile({
89+
name: "utf8.txt",
90+
content,
91+
});
92+
const end = writer.end();
93+
94+
const files = await extractFiles(writer.readable);
95+
await end;
96+
expect(files.get("utf8.txt")?.toString()).toBe(content);
97+
});
98+
99+
it("writes Uint8Array content", async () => {
100+
const writer = new FileWriter();
101+
const content = new TextEncoder().encode("Hello world");
102+
await writer.addFile({
103+
name: "hello.txt",
104+
content,
105+
});
106+
const end = writer.end();
107+
108+
const files = await extractFiles(writer.readable);
109+
await end;
110+
expect(files.get("hello.txt")?.toString()).toBe("Hello world");
111+
});
112+
113+
it("writes multi-byte UTF-8 Uint8Array content", async () => {
114+
const writer = new FileWriter();
115+
const text = "café ☕ — Grüße aus München 🌍 日本語テスト";
116+
const content = new TextEncoder().encode(text);
117+
await writer.addFile({
118+
name: "utf8.txt",
119+
content,
120+
});
121+
const end = writer.end();
122+
123+
const files = await extractFiles(writer.readable);
124+
await end;
125+
expect(files.get("utf8.txt")?.toString()).toBe(text);
126+
});
127+
69128
it("writes stream content with explicit size", async () => {
70129
const content = Buffer.from("streamed content");
71130
const writer = new FileWriter();
@@ -74,9 +133,10 @@ describe("FileWriter", () => {
74133
content: Readable.from(content),
75134
size: content.length,
76135
});
77-
writer.end();
136+
const end = writer.end();
78137

79138
const files = await extractFiles(writer.readable);
139+
await end;
80140
expect(files.get("stream.txt")?.toString()).toBe("streamed content");
81141
});
82142
});

packages/vercel-sandbox/src/api-client/file-writer.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import zlib from "zlib";
22
import tar, { type Pack } from "tar-stream";
33
import { Readable } from "stream";
44

5-
interface FileBuffer {
5+
interface FileData {
66
/**
77
* The name (path) of the file to write.
88
*/
99
name: string;
1010
/**
11-
* The content of the file as a Buffer.
11+
* The content of the file.
1212
*/
13-
content: Buffer;
13+
content: string | Uint8Array;
1414
/**
1515
* The file mode (permissions) to set on the file.
1616
* For example, 0o755 for executable files.
@@ -62,12 +62,16 @@ export class FileWriter {
6262
* Returns a Promise resolved once the file is written in the
6363
* stream.
6464
*/
65-
async addFile(file: FileBuffer | FileStream) {
65+
async addFile(file: FileData | FileStream) {
6666
return new Promise<void>((resolve, reject) => {
6767
const entry = this.pack.entry(
6868
"size" in file
6969
? { name: file.name, size: file.size, mode: file.mode }
70-
: { name: file.name, size: Buffer.byteLength(file.content), mode: file.mode },
70+
: {
71+
name: file.name,
72+
size: Buffer.byteLength(file.content),
73+
mode: file.mode,
74+
},
7175
(error) => {
7276
if (error) {
7377
return reject(error);

packages/vercel-sandbox/src/sandbox.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,11 +800,15 @@ export class Sandbox {
800800
* @example
801801
* // Write an executable script
802802
* await sandbox.writeFiles([
803-
* { path: "/usr/local/bin/myscript", content: Buffer.from("#!/bin/bash\necho hello"), mode: 0o755 }
803+
* { path: "/usr/local/bin/myscript", content: "#!/bin/bash\necho hello", mode: 0o755 }
804804
* ]);
805805
*/
806806
async writeFiles(
807-
files: { path: string; content: Buffer; mode?: number }[],
807+
files: {
808+
path: string;
809+
content: string | Uint8Array;
810+
mode?: number;
811+
}[],
808812
opts?: { signal?: AbortSignal },
809813
) {
810814
return this.withResume(

0 commit comments

Comments
 (0)