@cloudflare/sandbox 0.0.0-211d237 → 0.0.0-24ceb90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +3 -0
- package/container_src/index.ts +289 -3
- package/package.json +5 -3
- package/src/client.ts +229 -3
- package/src/index.ts +50 -0
- package/tests/test2.ts +219 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @cloudflare/sandbox
|
|
2
|
+
|
|
3
|
+
## 0.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#5](https://github.com/cloudflare/sandbox-sdk/pull/5) [`7c15b81`](https://github.com/cloudflare/sandbox-sdk/commit/7c15b817899e4d9e1f25747aaf439e5e9e880d15) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Make package ready for deployment
|
|
8
|
+
|
|
9
|
+
## 0.0.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`c0d9d33`](https://github.com/cloudflare/sandbox-sdk/commit/c0d9d3396badee1eab45e6b4a73d48957f31409b) Thanks [@threepointone](https://github.com/threepointone)! - actually work
|
|
14
|
+
|
|
15
|
+
- [`444d2da`](https://github.com/cloudflare/sandbox-sdk/commit/444d2dafde9a0f190e50c879b0e768da1b289b51) Thanks [@threepointone](https://github.com/threepointone)! - add experimental label
|
|
16
|
+
|
|
17
|
+
## 0.0.3
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [`2b087c4`](https://github.com/cloudflare/sandbox-sdk/commit/2b087c40a29697c20dad19b4e3b8512f5d404bd3) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Fix worker unable to find container port
|
|
22
|
+
|
|
23
|
+
## 0.0.2
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- [`52f02f0`](https://github.com/cloudflare/sandbox-sdk/commit/52f02f0625ef9f8eac695e51f93fa79651c0206d) Thanks [@threepointone](https://github.com/threepointone)! - readFile
|
|
28
|
+
|
|
29
|
+
## 0.0.1
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- [`f786c3c`](https://github.com/cloudflare/sandbox-sdk/commit/f786c3cee6bd9777bd74918ae9fdf381aa99f913) Thanks [@threepointone](https://github.com/threepointone)! - Release!
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
## @cloudflare/sandbox
|
|
2
2
|
|
|
3
|
+
> **⚠️ Experimental** - This library is currently experimental and we're actively seeking feedback. Please try it out and let us know what you think!
|
|
4
|
+
|
|
3
5
|
A library to spin up a sandboxed environment.
|
|
4
6
|
|
|
5
7
|
First, setup your wrangler.json to use the sandbox:
|
|
@@ -56,6 +58,7 @@ export default {
|
|
|
56
58
|
- `gitCheckout(repoUrl: string, options: { branch?: string; targetDir?: string; stream?: boolean })`: Checkout a git repository in the sandbox.
|
|
57
59
|
- `mkdir(path: string, options: { recursive?: boolean; stream?: boolean })`: Create a directory in the sandbox.
|
|
58
60
|
- `writeFile(path: string, content: string, options: { encoding?: string; stream?: boolean })`: Write content to a file in the sandbox.
|
|
61
|
+
- `readFile(path: string, options: { encoding?: string; stream?: boolean })`: Read content from a file in the sandbox.
|
|
59
62
|
- `deleteFile(path: string, options?: { stream?: boolean })`: Delete a file from the sandbox.
|
|
60
63
|
- `renameFile(oldPath: string, newPath: string, options?: { stream?: boolean })`: Rename a file in the sandbox.
|
|
61
64
|
- `moveFile(sourcePath: string, destinationPath: string, options?: { stream?: boolean })`: Move a file from one location to another in the sandbox.
|
package/container_src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import { mkdir, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { serve } from "bun";
|
|
5
5
|
|
|
@@ -28,6 +28,12 @@ interface WriteFileRequest {
|
|
|
28
28
|
sessionId?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
interface ReadFileRequest {
|
|
32
|
+
path: string;
|
|
33
|
+
encoding?: string;
|
|
34
|
+
sessionId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
interface DeleteFileRequest {
|
|
32
38
|
path: string;
|
|
33
39
|
sessionId?: string;
|
|
@@ -78,6 +84,8 @@ const server = serve({
|
|
|
78
84
|
const url = new URL(req.url);
|
|
79
85
|
const pathname = url.pathname;
|
|
80
86
|
|
|
87
|
+
console.log(`[Container] Incoming ${req.method} request to ${pathname}`);
|
|
88
|
+
|
|
81
89
|
// Handle CORS
|
|
82
90
|
const corsHeaders = {
|
|
83
91
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
@@ -87,11 +95,13 @@ const server = serve({
|
|
|
87
95
|
|
|
88
96
|
// Handle preflight requests
|
|
89
97
|
if (req.method === "OPTIONS") {
|
|
98
|
+
console.log(`[Container] Handling CORS preflight for ${pathname}`);
|
|
90
99
|
return new Response(null, { headers: corsHeaders, status: 200 });
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
try {
|
|
94
103
|
// Handle different routes
|
|
104
|
+
console.log(`[Container] Processing ${req.method} ${pathname}`);
|
|
95
105
|
switch (pathname) {
|
|
96
106
|
case "/":
|
|
97
107
|
return new Response("Hello from Bun server! 🚀", {
|
|
@@ -297,6 +307,18 @@ const server = serve({
|
|
|
297
307
|
}
|
|
298
308
|
break;
|
|
299
309
|
|
|
310
|
+
case "/api/read":
|
|
311
|
+
if (req.method === "POST") {
|
|
312
|
+
return handleReadFileRequest(req, corsHeaders);
|
|
313
|
+
}
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case "/api/read/stream":
|
|
317
|
+
if (req.method === "POST") {
|
|
318
|
+
return handleStreamingReadFileRequest(req, corsHeaders);
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
|
|
300
322
|
case "/api/delete":
|
|
301
323
|
if (req.method === "POST") {
|
|
302
324
|
return handleDeleteFileRequest(req, corsHeaders);
|
|
@@ -334,13 +356,14 @@ const server = serve({
|
|
|
334
356
|
break;
|
|
335
357
|
|
|
336
358
|
default:
|
|
359
|
+
console.log(`[Container] Route not found: ${pathname}`);
|
|
337
360
|
return new Response("Not Found", {
|
|
338
361
|
headers: corsHeaders,
|
|
339
362
|
status: 404,
|
|
340
363
|
});
|
|
341
364
|
}
|
|
342
365
|
} catch (error) {
|
|
343
|
-
console.error(
|
|
366
|
+
console.error(`[Container] Error handling ${req.method} ${pathname}:`, error);
|
|
344
367
|
return new Response(
|
|
345
368
|
JSON.stringify({
|
|
346
369
|
error: "Internal server error",
|
|
@@ -356,6 +379,7 @@ const server = serve({
|
|
|
356
379
|
);
|
|
357
380
|
}
|
|
358
381
|
},
|
|
382
|
+
hostname: "0.0.0.0",
|
|
359
383
|
port: 3000,
|
|
360
384
|
} as any);
|
|
361
385
|
|
|
@@ -1476,6 +1500,235 @@ async function handleStreamingWriteFileRequest(
|
|
|
1476
1500
|
}
|
|
1477
1501
|
}
|
|
1478
1502
|
|
|
1503
|
+
async function handleReadFileRequest(
|
|
1504
|
+
req: Request,
|
|
1505
|
+
corsHeaders: Record<string, string>
|
|
1506
|
+
): Promise<Response> {
|
|
1507
|
+
try {
|
|
1508
|
+
const body = (await req.json()) as ReadFileRequest;
|
|
1509
|
+
const { path, encoding = "utf-8", sessionId } = body;
|
|
1510
|
+
|
|
1511
|
+
if (!path || typeof path !== "string") {
|
|
1512
|
+
return new Response(
|
|
1513
|
+
JSON.stringify({
|
|
1514
|
+
error: "Path is required and must be a string",
|
|
1515
|
+
}),
|
|
1516
|
+
{
|
|
1517
|
+
headers: {
|
|
1518
|
+
"Content-Type": "application/json",
|
|
1519
|
+
...corsHeaders,
|
|
1520
|
+
},
|
|
1521
|
+
status: 400,
|
|
1522
|
+
}
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
// Basic safety check - prevent dangerous paths
|
|
1527
|
+
const dangerousPatterns = [
|
|
1528
|
+
/^\/$/, // Root directory
|
|
1529
|
+
/^\/etc/, // System directories
|
|
1530
|
+
/^\/var/, // System directories
|
|
1531
|
+
/^\/usr/, // System directories
|
|
1532
|
+
/^\/bin/, // System directories
|
|
1533
|
+
/^\/sbin/, // System directories
|
|
1534
|
+
/^\/boot/, // System directories
|
|
1535
|
+
/^\/dev/, // System directories
|
|
1536
|
+
/^\/proc/, // System directories
|
|
1537
|
+
/^\/sys/, // System directories
|
|
1538
|
+
/^\/tmp\/\.\./, // Path traversal attempts
|
|
1539
|
+
/\.\./, // Path traversal attempts
|
|
1540
|
+
];
|
|
1541
|
+
|
|
1542
|
+
if (dangerousPatterns.some((pattern) => pattern.test(path))) {
|
|
1543
|
+
return new Response(
|
|
1544
|
+
JSON.stringify({
|
|
1545
|
+
error: "Dangerous path not allowed",
|
|
1546
|
+
}),
|
|
1547
|
+
{
|
|
1548
|
+
headers: {
|
|
1549
|
+
"Content-Type": "application/json",
|
|
1550
|
+
...corsHeaders,
|
|
1551
|
+
},
|
|
1552
|
+
status: 400,
|
|
1553
|
+
}
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
console.log(`[Server] Reading file: ${path}`);
|
|
1558
|
+
|
|
1559
|
+
const result = await executeReadFile(path, encoding, sessionId);
|
|
1560
|
+
|
|
1561
|
+
return new Response(
|
|
1562
|
+
JSON.stringify({
|
|
1563
|
+
content: result.content,
|
|
1564
|
+
exitCode: result.exitCode,
|
|
1565
|
+
path,
|
|
1566
|
+
success: result.success,
|
|
1567
|
+
timestamp: new Date().toISOString(),
|
|
1568
|
+
}),
|
|
1569
|
+
{
|
|
1570
|
+
headers: {
|
|
1571
|
+
"Content-Type": "application/json",
|
|
1572
|
+
...corsHeaders,
|
|
1573
|
+
},
|
|
1574
|
+
}
|
|
1575
|
+
);
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
console.error("[Server] Error in handleReadFileRequest:", error);
|
|
1578
|
+
return new Response(
|
|
1579
|
+
JSON.stringify({
|
|
1580
|
+
error: "Failed to read file",
|
|
1581
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
1582
|
+
}),
|
|
1583
|
+
{
|
|
1584
|
+
headers: {
|
|
1585
|
+
"Content-Type": "application/json",
|
|
1586
|
+
...corsHeaders,
|
|
1587
|
+
},
|
|
1588
|
+
status: 500,
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
async function handleStreamingReadFileRequest(
|
|
1595
|
+
req: Request,
|
|
1596
|
+
corsHeaders: Record<string, string>
|
|
1597
|
+
): Promise<Response> {
|
|
1598
|
+
try {
|
|
1599
|
+
const body = (await req.json()) as ReadFileRequest;
|
|
1600
|
+
const { path, encoding = "utf-8", sessionId } = body;
|
|
1601
|
+
|
|
1602
|
+
if (!path || typeof path !== "string") {
|
|
1603
|
+
return new Response(
|
|
1604
|
+
JSON.stringify({
|
|
1605
|
+
error: "Path is required and must be a string",
|
|
1606
|
+
}),
|
|
1607
|
+
{
|
|
1608
|
+
headers: {
|
|
1609
|
+
"Content-Type": "application/json",
|
|
1610
|
+
...corsHeaders,
|
|
1611
|
+
},
|
|
1612
|
+
status: 400,
|
|
1613
|
+
}
|
|
1614
|
+
);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// Basic safety check - prevent dangerous paths
|
|
1618
|
+
const dangerousPatterns = [
|
|
1619
|
+
/^\/$/, // Root directory
|
|
1620
|
+
/^\/etc/, // System directories
|
|
1621
|
+
/^\/var/, // System directories
|
|
1622
|
+
/^\/usr/, // System directories
|
|
1623
|
+
/^\/bin/, // System directories
|
|
1624
|
+
/^\/sbin/, // System directories
|
|
1625
|
+
/^\/boot/, // System directories
|
|
1626
|
+
/^\/dev/, // System directories
|
|
1627
|
+
/^\/proc/, // System directories
|
|
1628
|
+
/^\/sys/, // System directories
|
|
1629
|
+
/^\/tmp\/\.\./, // Path traversal attempts
|
|
1630
|
+
/\.\./, // Path traversal attempts
|
|
1631
|
+
];
|
|
1632
|
+
|
|
1633
|
+
if (dangerousPatterns.some((pattern) => pattern.test(path))) {
|
|
1634
|
+
return new Response(
|
|
1635
|
+
JSON.stringify({
|
|
1636
|
+
error: "Dangerous path not allowed",
|
|
1637
|
+
}),
|
|
1638
|
+
{
|
|
1639
|
+
headers: {
|
|
1640
|
+
"Content-Type": "application/json",
|
|
1641
|
+
...corsHeaders,
|
|
1642
|
+
},
|
|
1643
|
+
status: 400,
|
|
1644
|
+
}
|
|
1645
|
+
);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
console.log(`[Server] Reading file (streaming): ${path}`);
|
|
1649
|
+
|
|
1650
|
+
const stream = new ReadableStream({
|
|
1651
|
+
start(controller) {
|
|
1652
|
+
(async () => {
|
|
1653
|
+
try {
|
|
1654
|
+
// Send command start event
|
|
1655
|
+
controller.enqueue(
|
|
1656
|
+
new TextEncoder().encode(
|
|
1657
|
+
`data: ${JSON.stringify({
|
|
1658
|
+
path,
|
|
1659
|
+
timestamp: new Date().toISOString(),
|
|
1660
|
+
type: "command_start",
|
|
1661
|
+
})}\n\n`
|
|
1662
|
+
)
|
|
1663
|
+
);
|
|
1664
|
+
|
|
1665
|
+
// Read the file
|
|
1666
|
+
const content = await readFile(path, {
|
|
1667
|
+
encoding: encoding as BufferEncoding,
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
console.log(`[Server] File read successfully: ${path}`);
|
|
1671
|
+
|
|
1672
|
+
// Send command completion event
|
|
1673
|
+
controller.enqueue(
|
|
1674
|
+
new TextEncoder().encode(
|
|
1675
|
+
`data: ${JSON.stringify({
|
|
1676
|
+
content,
|
|
1677
|
+
path,
|
|
1678
|
+
success: true,
|
|
1679
|
+
timestamp: new Date().toISOString(),
|
|
1680
|
+
type: "command_complete",
|
|
1681
|
+
})}\n\n`
|
|
1682
|
+
)
|
|
1683
|
+
);
|
|
1684
|
+
|
|
1685
|
+
controller.close();
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
console.error(`[Server] Error reading file: ${path}`, error);
|
|
1688
|
+
|
|
1689
|
+
controller.enqueue(
|
|
1690
|
+
new TextEncoder().encode(
|
|
1691
|
+
`data: ${JSON.stringify({
|
|
1692
|
+
error:
|
|
1693
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
1694
|
+
path,
|
|
1695
|
+
type: "error",
|
|
1696
|
+
})}\n\n`
|
|
1697
|
+
)
|
|
1698
|
+
);
|
|
1699
|
+
|
|
1700
|
+
controller.close();
|
|
1701
|
+
}
|
|
1702
|
+
})();
|
|
1703
|
+
},
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
return new Response(stream, {
|
|
1707
|
+
headers: {
|
|
1708
|
+
"Cache-Control": "no-cache",
|
|
1709
|
+
Connection: "keep-alive",
|
|
1710
|
+
"Content-Type": "text/event-stream",
|
|
1711
|
+
...corsHeaders,
|
|
1712
|
+
},
|
|
1713
|
+
});
|
|
1714
|
+
} catch (error) {
|
|
1715
|
+
console.error("[Server] Error in handleStreamingReadFileRequest:", error);
|
|
1716
|
+
return new Response(
|
|
1717
|
+
JSON.stringify({
|
|
1718
|
+
error: "Failed to read file",
|
|
1719
|
+
message: error instanceof Error ? error.message : "Unknown error",
|
|
1720
|
+
}),
|
|
1721
|
+
{
|
|
1722
|
+
headers: {
|
|
1723
|
+
"Content-Type": "application/json",
|
|
1724
|
+
...corsHeaders,
|
|
1725
|
+
},
|
|
1726
|
+
status: 500,
|
|
1727
|
+
}
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1479
1732
|
async function handleDeleteFileRequest(
|
|
1480
1733
|
req: Request,
|
|
1481
1734
|
corsHeaders: Record<string, string>
|
|
@@ -2506,6 +2759,37 @@ function executeWriteFile(
|
|
|
2506
2759
|
});
|
|
2507
2760
|
}
|
|
2508
2761
|
|
|
2762
|
+
function executeReadFile(
|
|
2763
|
+
path: string,
|
|
2764
|
+
encoding: string,
|
|
2765
|
+
sessionId?: string
|
|
2766
|
+
): Promise<{
|
|
2767
|
+
success: boolean;
|
|
2768
|
+
exitCode: number;
|
|
2769
|
+
content: string;
|
|
2770
|
+
}> {
|
|
2771
|
+
return new Promise((resolve, reject) => {
|
|
2772
|
+
(async () => {
|
|
2773
|
+
try {
|
|
2774
|
+
// Read the file
|
|
2775
|
+
const content = await readFile(path, {
|
|
2776
|
+
encoding: encoding as BufferEncoding,
|
|
2777
|
+
});
|
|
2778
|
+
|
|
2779
|
+
console.log(`[Server] File read successfully: ${path}`);
|
|
2780
|
+
resolve({
|
|
2781
|
+
content,
|
|
2782
|
+
exitCode: 0,
|
|
2783
|
+
success: true,
|
|
2784
|
+
});
|
|
2785
|
+
} catch (error) {
|
|
2786
|
+
console.error(`[Server] Error reading file: ${path}`, error);
|
|
2787
|
+
reject(error);
|
|
2788
|
+
}
|
|
2789
|
+
})();
|
|
2790
|
+
});
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2509
2793
|
function executeDeleteFile(
|
|
2510
2794
|
path: string,
|
|
2511
2795
|
sessionId?: string
|
|
@@ -2596,7 +2880,7 @@ function executeMoveFile(
|
|
|
2596
2880
|
});
|
|
2597
2881
|
}
|
|
2598
2882
|
|
|
2599
|
-
console.log(`🚀 Bun server running on http://
|
|
2883
|
+
console.log(`🚀 Bun server running on http://0.0.0.0:${server.port}`);
|
|
2600
2884
|
console.log(`📡 HTTP API endpoints available:`);
|
|
2601
2885
|
console.log(` POST /api/session/create - Create a new session`);
|
|
2602
2886
|
console.log(` GET /api/session/list - List all sessions`);
|
|
@@ -2610,6 +2894,8 @@ console.log(` POST /api/mkdir - Create a directory`);
|
|
|
2610
2894
|
console.log(` POST /api/mkdir/stream - Create a directory (streaming)`);
|
|
2611
2895
|
console.log(` POST /api/write - Write a file`);
|
|
2612
2896
|
console.log(` POST /api/write/stream - Write a file (streaming)`);
|
|
2897
|
+
console.log(` POST /api/read - Read a file`);
|
|
2898
|
+
console.log(` POST /api/read/stream - Read a file (streaming)`);
|
|
2613
2899
|
console.log(` POST /api/delete - Delete a file`);
|
|
2614
2900
|
console.log(` POST /api/delete/stream - Delete a file (streaming)`);
|
|
2615
2901
|
console.log(` POST /api/rename - Rename a file`);
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudflare/sandbox",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-24ceb90",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/cloudflare/sandbox-sdk"
|
|
7
7
|
},
|
|
8
8
|
"description": "A sandboxed environment for running commands",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@cloudflare/containers": "^0.0.
|
|
10
|
+
"@cloudflare/containers": "^0.0.13"
|
|
11
11
|
},
|
|
12
12
|
"tags": [
|
|
13
13
|
"sandbox",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"durable objects"
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
|
-
"build": "tsup src/*.ts --outDir dist --dts --sourcemap --format esm"
|
|
20
|
+
"build": "rm -rf dist && tsup src/*.ts --outDir dist --dts --sourcemap --format esm",
|
|
21
|
+
"docker:build": "docker build -t ghostwriternr/cloudflare-sandbox:$npm_package_version .",
|
|
22
|
+
"docker:publish": "docker push docker.io/ghostwriternr/cloudflare-sandbox:$npm_package_version"
|
|
21
23
|
},
|
|
22
24
|
"exports": {
|
|
23
25
|
".": {
|
package/src/client.ts
CHANGED
|
@@ -85,6 +85,20 @@ export interface WriteFileResponse {
|
|
|
85
85
|
timestamp: string;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
interface ReadFileRequest {
|
|
89
|
+
path: string;
|
|
90
|
+
encoding?: string;
|
|
91
|
+
sessionId?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface ReadFileResponse {
|
|
95
|
+
success: boolean;
|
|
96
|
+
exitCode: number;
|
|
97
|
+
path: string;
|
|
98
|
+
content: string;
|
|
99
|
+
timestamp: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
88
102
|
interface DeleteFileRequest {
|
|
89
103
|
path: string;
|
|
90
104
|
sessionId?: string;
|
|
@@ -142,6 +156,7 @@ interface StreamEvent {
|
|
|
142
156
|
newPath?: string;
|
|
143
157
|
sourcePath?: string;
|
|
144
158
|
destinationPath?: string;
|
|
159
|
+
content?: string;
|
|
145
160
|
success?: boolean;
|
|
146
161
|
exitCode?: number;
|
|
147
162
|
stdout?: string;
|
|
@@ -188,10 +203,31 @@ export class HttpClient {
|
|
|
188
203
|
path: string,
|
|
189
204
|
options?: RequestInit
|
|
190
205
|
): Promise<Response> {
|
|
191
|
-
|
|
192
|
-
|
|
206
|
+
const url = this.options.stub ? `stub:${path}` : `${this.baseUrl}${path}`;
|
|
207
|
+
const method = options?.method || "GET";
|
|
208
|
+
|
|
209
|
+
console.log(`[HTTP Client] Making ${method} request to ${url}`);
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
let response: Response;
|
|
213
|
+
|
|
214
|
+
if (this.options.stub) {
|
|
215
|
+
response = await this.options.stub.containerFetch(path, options, this.options.port);
|
|
216
|
+
} else {
|
|
217
|
+
response = await fetch(this.baseUrl + path, options);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(`[HTTP Client] Response: ${response.status} ${response.statusText}`);
|
|
221
|
+
|
|
222
|
+
if (!response.ok) {
|
|
223
|
+
console.error(`[HTTP Client] Request failed: ${method} ${url} - ${response.status} ${response.statusText}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return response;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error(`[HTTP Client] Request error: ${method} ${url}`, error);
|
|
229
|
+
throw error;
|
|
193
230
|
}
|
|
194
|
-
return fetch(this.baseUrl + path, options);
|
|
195
231
|
}
|
|
196
232
|
// Public methods to set event handlers
|
|
197
233
|
setOnOutput(
|
|
@@ -978,6 +1014,164 @@ export class HttpClient {
|
|
|
978
1014
|
}
|
|
979
1015
|
}
|
|
980
1016
|
|
|
1017
|
+
async readFile(
|
|
1018
|
+
path: string,
|
|
1019
|
+
encoding: string = "utf-8",
|
|
1020
|
+
sessionId?: string
|
|
1021
|
+
): Promise<ReadFileResponse> {
|
|
1022
|
+
try {
|
|
1023
|
+
const targetSessionId = sessionId || this.sessionId;
|
|
1024
|
+
|
|
1025
|
+
const response = await this.doFetch(`/api/read`, {
|
|
1026
|
+
body: JSON.stringify({
|
|
1027
|
+
encoding,
|
|
1028
|
+
path,
|
|
1029
|
+
sessionId: targetSessionId,
|
|
1030
|
+
}),
|
|
1031
|
+
headers: {
|
|
1032
|
+
"Content-Type": "application/json",
|
|
1033
|
+
},
|
|
1034
|
+
method: "POST",
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
if (!response.ok) {
|
|
1038
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1039
|
+
error?: string;
|
|
1040
|
+
};
|
|
1041
|
+
throw new Error(
|
|
1042
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
const data: ReadFileResponse = await response.json();
|
|
1047
|
+
console.log(
|
|
1048
|
+
`[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
|
|
1049
|
+
);
|
|
1050
|
+
|
|
1051
|
+
return data;
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
console.error("[HTTP Client] Error reading file:", error);
|
|
1054
|
+
throw error;
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
async readFileStream(
|
|
1059
|
+
path: string,
|
|
1060
|
+
encoding: string = "utf-8",
|
|
1061
|
+
sessionId?: string
|
|
1062
|
+
): Promise<void> {
|
|
1063
|
+
try {
|
|
1064
|
+
const targetSessionId = sessionId || this.sessionId;
|
|
1065
|
+
|
|
1066
|
+
const response = await this.doFetch(`/api/read/stream`, {
|
|
1067
|
+
body: JSON.stringify({
|
|
1068
|
+
encoding,
|
|
1069
|
+
path,
|
|
1070
|
+
sessionId: targetSessionId,
|
|
1071
|
+
}),
|
|
1072
|
+
headers: {
|
|
1073
|
+
"Content-Type": "application/json",
|
|
1074
|
+
},
|
|
1075
|
+
method: "POST",
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
if (!response.ok) {
|
|
1079
|
+
const errorData = (await response.json().catch(() => ({}))) as {
|
|
1080
|
+
error?: string;
|
|
1081
|
+
};
|
|
1082
|
+
throw new Error(
|
|
1083
|
+
errorData.error || `HTTP error! status: ${response.status}`
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
if (!response.body) {
|
|
1088
|
+
throw new Error("No response body for streaming request");
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
const reader = response.body.getReader();
|
|
1092
|
+
const decoder = new TextDecoder();
|
|
1093
|
+
|
|
1094
|
+
try {
|
|
1095
|
+
while (true) {
|
|
1096
|
+
const { done, value } = await reader.read();
|
|
1097
|
+
|
|
1098
|
+
if (done) {
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1103
|
+
const lines = chunk.split("\n");
|
|
1104
|
+
|
|
1105
|
+
for (const line of lines) {
|
|
1106
|
+
if (line.startsWith("data: ")) {
|
|
1107
|
+
try {
|
|
1108
|
+
const eventData = line.slice(6); // Remove 'data: ' prefix
|
|
1109
|
+
const event: StreamEvent = JSON.parse(eventData);
|
|
1110
|
+
|
|
1111
|
+
console.log(
|
|
1112
|
+
`[HTTP Client] Read file stream event: ${event.type}`
|
|
1113
|
+
);
|
|
1114
|
+
this.options.onStreamEvent?.(event);
|
|
1115
|
+
|
|
1116
|
+
switch (event.type) {
|
|
1117
|
+
case "command_start":
|
|
1118
|
+
console.log(
|
|
1119
|
+
`[HTTP Client] Read file started: ${event.path}`
|
|
1120
|
+
);
|
|
1121
|
+
this.options.onCommandStart?.("read", [path, encoding]);
|
|
1122
|
+
break;
|
|
1123
|
+
|
|
1124
|
+
case "command_complete":
|
|
1125
|
+
console.log(
|
|
1126
|
+
`[HTTP Client] Read file completed: ${
|
|
1127
|
+
event.path
|
|
1128
|
+
}, Success: ${event.success}, Content length: ${
|
|
1129
|
+
event.content?.length || 0
|
|
1130
|
+
}`
|
|
1131
|
+
);
|
|
1132
|
+
this.options.onCommandComplete?.(
|
|
1133
|
+
event.success!,
|
|
1134
|
+
0,
|
|
1135
|
+
event.content || "",
|
|
1136
|
+
"",
|
|
1137
|
+
"read",
|
|
1138
|
+
[path, encoding]
|
|
1139
|
+
);
|
|
1140
|
+
break;
|
|
1141
|
+
|
|
1142
|
+
case "error":
|
|
1143
|
+
console.error(
|
|
1144
|
+
`[HTTP Client] Read file error: ${event.error}`
|
|
1145
|
+
);
|
|
1146
|
+
this.options.onError?.(event.error!, "read", [
|
|
1147
|
+
path,
|
|
1148
|
+
encoding,
|
|
1149
|
+
]);
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
} catch (parseError) {
|
|
1153
|
+
console.warn(
|
|
1154
|
+
"[HTTP Client] Failed to parse read file stream event:",
|
|
1155
|
+
parseError
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
} finally {
|
|
1162
|
+
reader.releaseLock();
|
|
1163
|
+
}
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
console.error("[HTTP Client] Error in streaming read file:", error);
|
|
1166
|
+
this.options.onError?.(
|
|
1167
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
1168
|
+
"read",
|
|
1169
|
+
[path, encoding]
|
|
1170
|
+
);
|
|
1171
|
+
throw error;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
981
1175
|
async deleteFile(
|
|
982
1176
|
path: string,
|
|
983
1177
|
sessionId?: string
|
|
@@ -1629,6 +1823,38 @@ export async function quickWriteFileStream(
|
|
|
1629
1823
|
}
|
|
1630
1824
|
}
|
|
1631
1825
|
|
|
1826
|
+
// Convenience function for quick file reading
|
|
1827
|
+
export async function quickReadFile(
|
|
1828
|
+
path: string,
|
|
1829
|
+
encoding: string = "utf-8",
|
|
1830
|
+
options?: HttpClientOptions
|
|
1831
|
+
): Promise<ReadFileResponse> {
|
|
1832
|
+
const client = createClient(options);
|
|
1833
|
+
await client.createSession();
|
|
1834
|
+
|
|
1835
|
+
try {
|
|
1836
|
+
return await client.readFile(path, encoding);
|
|
1837
|
+
} finally {
|
|
1838
|
+
client.clearSession();
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// Convenience function for quick streaming file reading
|
|
1843
|
+
export async function quickReadFileStream(
|
|
1844
|
+
path: string,
|
|
1845
|
+
encoding: string = "utf-8",
|
|
1846
|
+
options?: HttpClientOptions
|
|
1847
|
+
): Promise<void> {
|
|
1848
|
+
const client = createClient(options);
|
|
1849
|
+
await client.createSession();
|
|
1850
|
+
|
|
1851
|
+
try {
|
|
1852
|
+
await client.readFileStream(path, encoding);
|
|
1853
|
+
} finally {
|
|
1854
|
+
client.clearSession();
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1632
1858
|
// Convenience function for quick file deletion
|
|
1633
1859
|
export async function quickDeleteFile(
|
|
1634
1860
|
path: string,
|
package/src/index.ts
CHANGED
|
@@ -76,4 +76,54 @@ export class Sandbox<Env = unknown> extends Container<Env> {
|
|
|
76
76
|
}
|
|
77
77
|
return this.client.mkdir(path, options.recursive);
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
async writeFile(
|
|
81
|
+
path: string,
|
|
82
|
+
content: string,
|
|
83
|
+
options: { encoding?: string; stream?: boolean }
|
|
84
|
+
) {
|
|
85
|
+
if (options?.stream) {
|
|
86
|
+
return this.client.writeFileStream(path, content, options.encoding);
|
|
87
|
+
}
|
|
88
|
+
return this.client.writeFile(path, content, options.encoding);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async deleteFile(path: string, options: { stream?: boolean }) {
|
|
92
|
+
if (options?.stream) {
|
|
93
|
+
return this.client.deleteFileStream(path);
|
|
94
|
+
}
|
|
95
|
+
return this.client.deleteFile(path);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async renameFile(
|
|
99
|
+
oldPath: string,
|
|
100
|
+
newPath: string,
|
|
101
|
+
options: { stream?: boolean }
|
|
102
|
+
) {
|
|
103
|
+
if (options?.stream) {
|
|
104
|
+
return this.client.renameFileStream(oldPath, newPath);
|
|
105
|
+
}
|
|
106
|
+
return this.client.renameFile(oldPath, newPath);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async moveFile(
|
|
110
|
+
sourcePath: string,
|
|
111
|
+
destinationPath: string,
|
|
112
|
+
options: { stream?: boolean }
|
|
113
|
+
) {
|
|
114
|
+
if (options?.stream) {
|
|
115
|
+
return this.client.moveFileStream(sourcePath, destinationPath);
|
|
116
|
+
}
|
|
117
|
+
return this.client.moveFile(sourcePath, destinationPath);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async readFile(
|
|
121
|
+
path: string,
|
|
122
|
+
options: { encoding?: string; stream?: boolean }
|
|
123
|
+
) {
|
|
124
|
+
if (options?.stream) {
|
|
125
|
+
return this.client.readFileStream(path, options.encoding);
|
|
126
|
+
}
|
|
127
|
+
return this.client.readFile(path, options.encoding);
|
|
128
|
+
}
|
|
79
129
|
}
|
package/tests/test2.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
quickExecuteStream,
|
|
8
8
|
quickMoveFile,
|
|
9
9
|
quickMoveFileStream,
|
|
10
|
+
quickReadFile,
|
|
11
|
+
quickReadFileStream,
|
|
10
12
|
quickRenameFile,
|
|
11
13
|
quickRenameFileStream,
|
|
12
14
|
quickWriteFile,
|
|
@@ -683,6 +685,223 @@ async function testHttpClient() {
|
|
|
683
685
|
);
|
|
684
686
|
}
|
|
685
687
|
|
|
688
|
+
// Test 25: File reading
|
|
689
|
+
console.log("Test 25: File reading");
|
|
690
|
+
try {
|
|
691
|
+
// First create a file to read
|
|
692
|
+
const readContent = "This is a test file for reading\nLine 2\nLine 3";
|
|
693
|
+
await quickWriteFile("file-to-read.txt", readContent);
|
|
694
|
+
console.log("✅ Test file created for reading");
|
|
695
|
+
|
|
696
|
+
// Read the file
|
|
697
|
+
const result = await quickReadFile("file-to-read.txt");
|
|
698
|
+
console.log("✅ File read successfully:", result.success);
|
|
699
|
+
console.log(" Path:", result.path);
|
|
700
|
+
console.log(" Content length:", result.content.length, "characters");
|
|
701
|
+
console.log(" Exit code:", result.exitCode);
|
|
702
|
+
|
|
703
|
+
// Verify the content
|
|
704
|
+
console.log("✅ File content verified:", result.content === readContent);
|
|
705
|
+
console.log(" Content:", result.content);
|
|
706
|
+
console.log("✅ File reading test completed\n");
|
|
707
|
+
} catch (error) {
|
|
708
|
+
console.error("❌ Test 25 failed:", error);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Test 26: File reading with custom encoding
|
|
712
|
+
console.log("Test 26: File reading with custom encoding");
|
|
713
|
+
try {
|
|
714
|
+
// Create a file with special characters
|
|
715
|
+
const specialContent = "Hello 世界! 🌍 Test with emojis and unicode";
|
|
716
|
+
await quickWriteFile("special-chars.txt", specialContent, "utf-8");
|
|
717
|
+
console.log("✅ Special characters file created");
|
|
718
|
+
|
|
719
|
+
// Read with explicit UTF-8 encoding
|
|
720
|
+
const result = await quickReadFile("special-chars.txt", "utf-8");
|
|
721
|
+
console.log(
|
|
722
|
+
"✅ Special characters file read successfully:",
|
|
723
|
+
result.success
|
|
724
|
+
);
|
|
725
|
+
console.log(
|
|
726
|
+
"✅ Special characters verified:",
|
|
727
|
+
result.content === specialContent
|
|
728
|
+
);
|
|
729
|
+
console.log(" Content:", result.content);
|
|
730
|
+
console.log("✅ Custom encoding test completed\n");
|
|
731
|
+
} catch (error) {
|
|
732
|
+
console.error("❌ Test 26 failed:", error);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Test 27: File reading in nested directories
|
|
736
|
+
console.log("Test 27: File reading in nested directories");
|
|
737
|
+
try {
|
|
738
|
+
// Create a file in a nested directory
|
|
739
|
+
const nestedContent = "This file is in a nested directory for reading";
|
|
740
|
+
await quickWriteFile("nested/read/test-nested-read.txt", nestedContent);
|
|
741
|
+
console.log("✅ Nested file created for reading");
|
|
742
|
+
|
|
743
|
+
// Read the nested file
|
|
744
|
+
const result = await quickReadFile("nested/read/test-nested-read.txt");
|
|
745
|
+
console.log("✅ Nested file read successfully:", result.success);
|
|
746
|
+
console.log(
|
|
747
|
+
"✅ Nested file content verified:",
|
|
748
|
+
result.content === nestedContent
|
|
749
|
+
);
|
|
750
|
+
console.log(" Content:", result.content);
|
|
751
|
+
console.log("✅ Nested directory reading test completed\n");
|
|
752
|
+
} catch (error) {
|
|
753
|
+
console.error("❌ Test 27 failed:", error);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Test 28: Streaming file reading
|
|
757
|
+
console.log("Test 28: Streaming file reading");
|
|
758
|
+
try {
|
|
759
|
+
const client = createClient();
|
|
760
|
+
await client.createSession();
|
|
761
|
+
|
|
762
|
+
// Create a file to read via streaming
|
|
763
|
+
const streamReadContent =
|
|
764
|
+
"This file will be read via streaming\nLine 2\nLine 3";
|
|
765
|
+
await client.writeFile("stream-read-file.txt", streamReadContent);
|
|
766
|
+
console.log("✅ Test file created for streaming reading");
|
|
767
|
+
|
|
768
|
+
let readContent = "";
|
|
769
|
+
let readCompleted = false;
|
|
770
|
+
|
|
771
|
+
// Set up event handlers for streaming
|
|
772
|
+
client.setOnStreamEvent((event) => {
|
|
773
|
+
if (event.type === "command_complete" && event.content) {
|
|
774
|
+
readContent = event.content;
|
|
775
|
+
readCompleted = true;
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
console.log(" Starting streaming file reading...");
|
|
780
|
+
await client.readFileStream("stream-read-file.txt");
|
|
781
|
+
console.log("✅ Streaming file reading completed");
|
|
782
|
+
|
|
783
|
+
// Verify the content was read correctly
|
|
784
|
+
console.log(
|
|
785
|
+
"✅ Streaming read content verified:",
|
|
786
|
+
readContent === streamReadContent
|
|
787
|
+
);
|
|
788
|
+
console.log(" Content length:", readContent.length, "characters");
|
|
789
|
+
|
|
790
|
+
client.clearSession();
|
|
791
|
+
console.log("✅ Streaming file reading test completed\n");
|
|
792
|
+
} catch (error) {
|
|
793
|
+
console.error("❌ Test 28 failed:", error);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Test 29: Quick streaming file reading
|
|
797
|
+
console.log("Test 29: Quick streaming file reading");
|
|
798
|
+
try {
|
|
799
|
+
// Create a file for quick streaming read
|
|
800
|
+
const quickReadContent = "Quick streaming read test content";
|
|
801
|
+
await quickWriteFile("quick-read-stream.txt", quickReadContent);
|
|
802
|
+
console.log("✅ Test file created for quick streaming reading");
|
|
803
|
+
|
|
804
|
+
let quickReadContentReceived = "";
|
|
805
|
+
let quickReadCompleted = false;
|
|
806
|
+
|
|
807
|
+
console.log(" Starting quick streaming file reading...");
|
|
808
|
+
await quickReadFileStream("quick-read-stream.txt", "utf-8", {
|
|
809
|
+
onStreamEvent: (event) => {
|
|
810
|
+
if (event.type === "command_complete" && event.content) {
|
|
811
|
+
quickReadContentReceived = event.content;
|
|
812
|
+
quickReadCompleted = true;
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
});
|
|
816
|
+
console.log("✅ Quick streaming file reading completed");
|
|
817
|
+
|
|
818
|
+
// Verify the content
|
|
819
|
+
console.log(
|
|
820
|
+
"✅ Quick streaming read verified:",
|
|
821
|
+
quickReadContentReceived === quickReadContent
|
|
822
|
+
);
|
|
823
|
+
console.log(" Content:", quickReadContentReceived);
|
|
824
|
+
console.log("✅ Quick streaming file reading test completed\n");
|
|
825
|
+
} catch (error) {
|
|
826
|
+
console.error("❌ Test 29 failed:", error);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Test 30: File reading with session management
|
|
830
|
+
console.log("Test 30: File reading with session management");
|
|
831
|
+
try {
|
|
832
|
+
const client = createClient();
|
|
833
|
+
const sessionId = await client.createSession();
|
|
834
|
+
|
|
835
|
+
// Create a file in session
|
|
836
|
+
const sessionReadContent =
|
|
837
|
+
"This file was created and read with session management";
|
|
838
|
+
await client.writeFile(
|
|
839
|
+
"session-read-file.txt",
|
|
840
|
+
sessionReadContent,
|
|
841
|
+
"utf-8",
|
|
842
|
+
sessionId
|
|
843
|
+
);
|
|
844
|
+
console.log("✅ Session file created for reading");
|
|
845
|
+
|
|
846
|
+
// Read the file in the same session
|
|
847
|
+
const result = await client.readFile(
|
|
848
|
+
"session-read-file.txt",
|
|
849
|
+
"utf-8",
|
|
850
|
+
sessionId
|
|
851
|
+
);
|
|
852
|
+
console.log("✅ Session file read successfully:", result.success);
|
|
853
|
+
console.log(
|
|
854
|
+
"✅ Session file content verified:",
|
|
855
|
+
result.content === sessionReadContent
|
|
856
|
+
);
|
|
857
|
+
console.log(" Session ID:", sessionId);
|
|
858
|
+
console.log(" Content:", result.content);
|
|
859
|
+
|
|
860
|
+
client.clearSession();
|
|
861
|
+
console.log("✅ Session file reading test completed\n");
|
|
862
|
+
} catch (error) {
|
|
863
|
+
console.error("❌ Test 30 failed:", error);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Test 31: Error handling for file reading
|
|
867
|
+
console.log("Test 31: Error handling for file reading");
|
|
868
|
+
try {
|
|
869
|
+
// Try to read a non-existent file
|
|
870
|
+
await quickReadFile("non-existent-read-file.txt");
|
|
871
|
+
console.log("❌ Should have failed for non-existent file");
|
|
872
|
+
} catch (error) {
|
|
873
|
+
console.log("✅ Error handling works for non-existent files");
|
|
874
|
+
console.log(
|
|
875
|
+
" Error:",
|
|
876
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
try {
|
|
881
|
+
// Try to read from a dangerous path
|
|
882
|
+
await quickReadFile("/etc/passwd");
|
|
883
|
+
console.log("❌ Should have failed for dangerous path");
|
|
884
|
+
} catch (error) {
|
|
885
|
+
console.log("✅ Error handling works for dangerous paths");
|
|
886
|
+
console.log(
|
|
887
|
+
" Error:",
|
|
888
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
try {
|
|
893
|
+
// Try to read with empty path
|
|
894
|
+
await quickReadFile("");
|
|
895
|
+
console.log("❌ Should have failed for empty path");
|
|
896
|
+
} catch (error) {
|
|
897
|
+
console.log("✅ Error handling works for empty paths");
|
|
898
|
+
console.log(
|
|
899
|
+
" Error:",
|
|
900
|
+
error instanceof Error ? error.message : "Unknown error",
|
|
901
|
+
"\n"
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
|
|
686
905
|
console.log("🎉 All tests completed!");
|
|
687
906
|
}
|
|
688
907
|
|