@runko/sandbox-cloudflare 0.1.0
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/README.md +81 -0
- package/dist/index.cjs +308 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +307 -0
- package/dist/protocol-BXejIk9L.cjs +226 -0
- package/dist/protocol-Dj8Yv54w.js +101 -0
- package/dist/worker.cjs +279 -0
- package/dist/worker.d.cts +70 -0
- package/dist/worker.d.ts +70 -0
- package/dist/worker.js +278 -0
- package/package.json +63 -0
package/dist/worker.cjs
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_protocol = require("./protocol-BXejIk9L.cjs");
|
|
3
|
+
let _runko_virtual_fs = require("@runko/virtual-fs");
|
|
4
|
+
require("zod");
|
|
5
|
+
//#region src/worker.ts
|
|
6
|
+
/**
|
|
7
|
+
* `./worker`:Cloudflare Sandbox 网关(部署在宿主自己的 wrangler 项目里)。
|
|
8
|
+
* 把 `src/protocol.ts` 的八个端点翻译成对注入的 `CfSandboxLike` 的调用——本文件
|
|
9
|
+
* **零 `@cloudflare/sandbox` import**(该包加载依赖 `cloudflare:workers` 内置模块,
|
|
10
|
+
* Node 下不可加载,见 docs/tech/sandbox.md §8.2/§4"包本体零
|
|
11
|
+
* cloudflare import"):`CfSandboxLike` 是以该包 0.12.3 版本 `ISandbox` d.ts 为
|
|
12
|
+
* 蓝本手写的结构化最小子集,真实装配(`getSandbox(env.Sandbox, id)` 返回值天然
|
|
13
|
+
* 结构兼容本接口)留给宿主项目经 `createSandboxGateway({ getSandbox })` 注入。
|
|
14
|
+
*
|
|
15
|
+
* ---- 路径锚定:虚拟根 "/" ↔ 沙盒默认 cwd(工单裁量,见协议表 §8.3 附近注释)----
|
|
16
|
+
*
|
|
17
|
+
* `@cloudflare/sandbox` 的文件/目录方法把"/"开头的路径当作容器真实绝对路径、
|
|
18
|
+
* 把不带前导斜杠的路径当作相对于沙盒自身默认工作目录(其官方文档称之为
|
|
19
|
+
* "/workspace")解析——这与 E2B/Vercel 适配器"client 端 `opts.root` 选项做
|
|
20
|
+
* 虚拟根↔真实目录换算"的形状不同:Cloudflare 网关没有 root 配置(`cloudflareWorkspace`
|
|
21
|
+
* 的选项里也确实没有 root 字段),换算发生在**网关**这一侧、且是隐式的——
|
|
22
|
+
* `toSandboxPath()` 把客户端送来的虚拟绝对路径(`normalizePath()` 产出,形如
|
|
23
|
+
* "/" 或 "/a/b.txt")去掉前导斜杠变成沙盒相对路径("." 或 "a/b.txt"),从而让
|
|
24
|
+
* 沙盒自己的默认工作目录天然充当虚拟根——不需要任何一侧显式配置真实目录。
|
|
25
|
+
*/
|
|
26
|
+
function describeError(error) {
|
|
27
|
+
return error instanceof Error ? error.message : String(error);
|
|
28
|
+
}
|
|
29
|
+
function jsonResponse(body, status = 200) {
|
|
30
|
+
return new Response(JSON.stringify(body), {
|
|
31
|
+
status,
|
|
32
|
+
headers: { "content-type": "application/json" }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function errorResponse(status, code, message) {
|
|
36
|
+
return jsonResponse({
|
|
37
|
+
code,
|
|
38
|
+
message
|
|
39
|
+
}, status);
|
|
40
|
+
}
|
|
41
|
+
/** 虚拟绝对路径 → 沙盒路径:见文件头"路径锚定"注释。 */
|
|
42
|
+
function toSandboxPath(virtualPath) {
|
|
43
|
+
return virtualPath === "/" ? "." : virtualPath.slice(1);
|
|
44
|
+
}
|
|
45
|
+
function normalizeOrBadRequest(rawPath) {
|
|
46
|
+
try {
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
path: (0, _runko_virtual_fs.normalizePath)(rawPath)
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
response: errorResponse(400, "bad_request", `invalid path "${rawPath}": ${describeError(error)}`)
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function withValidatedBody(request, schema, handler) {
|
|
59
|
+
let raw;
|
|
60
|
+
try {
|
|
61
|
+
raw = await request.json();
|
|
62
|
+
} catch {
|
|
63
|
+
return errorResponse(400, "bad_request", "request body is not valid JSON");
|
|
64
|
+
}
|
|
65
|
+
const parsed = schema.safeParse(raw);
|
|
66
|
+
if (!parsed.success) return errorResponse(400, "bad_request", `request body failed validation: ${parsed.error.message}`);
|
|
67
|
+
return handler(parsed.data);
|
|
68
|
+
}
|
|
69
|
+
/** `read/mkdir/readdir/stat` 四个 `{ path }` 形状的端点共用:先校验+规范化 path 再交给业务 handler。 */
|
|
70
|
+
async function withNormalizedPath(body, handler) {
|
|
71
|
+
const normalized = normalizeOrBadRequest(body.path);
|
|
72
|
+
if (!normalized.ok) return normalized.response;
|
|
73
|
+
return handler(normalized.path, body);
|
|
74
|
+
}
|
|
75
|
+
/** stat("/") 没有对应的"父目录条目"可查——合成一个恒为目录的条目代表虚拟根本身。 */
|
|
76
|
+
const ROOT_ENTRY = {
|
|
77
|
+
name: "",
|
|
78
|
+
relativePath: "",
|
|
79
|
+
type: "directory",
|
|
80
|
+
size: 0,
|
|
81
|
+
modifiedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
82
|
+
};
|
|
83
|
+
/** 工单要求的 "stat 经 listFiles(dirname) 找条目" 策略——stat/readdir/rm 三个端点共用。 */
|
|
84
|
+
async function findEntry(sandbox, virtualPath) {
|
|
85
|
+
if (virtualPath === "/") return {
|
|
86
|
+
found: true,
|
|
87
|
+
entry: ROOT_ENTRY
|
|
88
|
+
};
|
|
89
|
+
const parentVirtual = (0, _runko_virtual_fs.dirname)(virtualPath);
|
|
90
|
+
let listing;
|
|
91
|
+
try {
|
|
92
|
+
listing = await sandbox.listFiles(toSandboxPath(parentVirtual), { recursive: false });
|
|
93
|
+
} catch {
|
|
94
|
+
return { found: false };
|
|
95
|
+
}
|
|
96
|
+
const name = (0, _runko_virtual_fs.basename)(virtualPath);
|
|
97
|
+
const entry = listing.files.find((f) => f.name === name);
|
|
98
|
+
return entry === void 0 ? { found: false } : {
|
|
99
|
+
found: true,
|
|
100
|
+
entry
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** `CfFileInfo.type` → `FileStat.type`:沙盒工作区没有 reference 概念(§3.1),symlink/other 一律按 file 呈现。 */
|
|
104
|
+
function mapEntryType(type) {
|
|
105
|
+
return type === "directory" ? "dir" : "file";
|
|
106
|
+
}
|
|
107
|
+
function toStatResponse(entry) {
|
|
108
|
+
const type = mapEntryType(entry.type);
|
|
109
|
+
const mtimeMs = Date.parse(entry.modifiedAt);
|
|
110
|
+
return {
|
|
111
|
+
type,
|
|
112
|
+
...type === "file" ? { size: entry.size } : {},
|
|
113
|
+
...Number.isFinite(mtimeMs) ? { mtime: mtimeMs } : {}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** existence/kind 查不出更具体原因时的兜底翻译:找不到→404,是目录→400 not_dir,否则→500 sandbox_error。 */
|
|
117
|
+
async function translateFsError(sandbox, virtualPath, error) {
|
|
118
|
+
const lookup = await findEntry(sandbox, virtualPath);
|
|
119
|
+
if (!lookup.found) return errorResponse(404, "not_found", `no such file or directory: "${virtualPath}"`);
|
|
120
|
+
if (lookup.entry.type === "directory") return errorResponse(400, "not_dir", `"${virtualPath}" is a directory: ${describeError(error)}`);
|
|
121
|
+
return errorResponse(500, "sandbox_error", `sandbox operation failed for "${virtualPath}": ${describeError(error)}`);
|
|
122
|
+
}
|
|
123
|
+
async function handleRead(sandbox, virtualPath) {
|
|
124
|
+
try {
|
|
125
|
+
return jsonResponse({ dataBase64: (await sandbox.readFile(toSandboxPath(virtualPath), { encoding: "base64" })).content });
|
|
126
|
+
} catch (error) {
|
|
127
|
+
return translateFsError(sandbox, virtualPath, error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function handleWrite(sandbox, virtualPath, dataBase64) {
|
|
131
|
+
try {
|
|
132
|
+
await sandbox.mkdir(toSandboxPath((0, _runko_virtual_fs.dirname)(virtualPath)), { recursive: true });
|
|
133
|
+
await sandbox.writeFile(toSandboxPath(virtualPath), dataBase64, { encoding: "base64" });
|
|
134
|
+
return jsonResponse({ ok: true });
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return translateFsError(sandbox, virtualPath, error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function handleMkdir(sandbox, virtualPath) {
|
|
140
|
+
try {
|
|
141
|
+
await sandbox.mkdir(toSandboxPath(virtualPath), { recursive: true });
|
|
142
|
+
return jsonResponse({ ok: true });
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return translateFsError(sandbox, virtualPath, error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function handleReaddir(sandbox, virtualPath) {
|
|
148
|
+
const lookup = await findEntry(sandbox, virtualPath);
|
|
149
|
+
if (!lookup.found) return errorResponse(404, "not_found", `no such file or directory: "${virtualPath}"`);
|
|
150
|
+
if (lookup.entry.type !== "directory") return errorResponse(400, "not_dir", `"${virtualPath}" is not a directory`);
|
|
151
|
+
try {
|
|
152
|
+
return jsonResponse({ entries: (await sandbox.listFiles(toSandboxPath(virtualPath), { recursive: false })).files.map((f) => ({
|
|
153
|
+
name: f.name,
|
|
154
|
+
type: mapEntryType(f.type)
|
|
155
|
+
})) });
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return errorResponse(500, "sandbox_error", `failed to list "${virtualPath}": ${describeError(error)}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function handleStat(sandbox, virtualPath) {
|
|
161
|
+
const lookup = await findEntry(sandbox, virtualPath);
|
|
162
|
+
if (!lookup.found) return errorResponse(404, "not_found", `no such file or directory: "${virtualPath}"`);
|
|
163
|
+
return jsonResponse(toStatResponse(lookup.entry));
|
|
164
|
+
}
|
|
165
|
+
async function handleGlob(sandbox, pattern) {
|
|
166
|
+
try {
|
|
167
|
+
return jsonResponse({ paths: (await sandbox.listFiles(".", { recursive: true })).files.filter((f) => f.type !== "directory").map((f) => `/${f.relativePath}`).filter((virtualPath) => (0, _runko_virtual_fs.matchesGlob)(pattern, virtualPath)).sort() });
|
|
168
|
+
} catch (error) {
|
|
169
|
+
return errorResponse(500, "sandbox_error", `glob failed for pattern "${pattern}": ${describeError(error)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async function handleRm(sandbox, virtualPath, recursive) {
|
|
173
|
+
if (virtualPath === "/") return errorResponse(400, "bad_request", "cannot remove the virtual root \"/\"");
|
|
174
|
+
const lookup = await findEntry(sandbox, virtualPath);
|
|
175
|
+
if (!lookup.found) return errorResponse(404, "not_found", `no such file or directory: "${virtualPath}"`);
|
|
176
|
+
const sandboxPath = toSandboxPath(virtualPath);
|
|
177
|
+
if (lookup.entry.type === "directory" && recursive !== true) {
|
|
178
|
+
let childCount = 0;
|
|
179
|
+
try {
|
|
180
|
+
childCount = (await sandbox.listFiles(sandboxPath, { recursive: false })).files.length;
|
|
181
|
+
} catch {
|
|
182
|
+
childCount = 0;
|
|
183
|
+
}
|
|
184
|
+
if (childCount > 0) return errorResponse(409, "dir_not_empty", `directory not empty (pass { recursive: true } to delete): "${virtualPath}"`);
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
await sandbox.deleteFile(sandboxPath);
|
|
188
|
+
return jsonResponse({ ok: true });
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return errorResponse(500, "sandbox_error", `failed to delete "${virtualPath}": ${describeError(error)}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const textEncoder = new TextEncoder();
|
|
194
|
+
function ndjsonLine(event) {
|
|
195
|
+
return textEncoder.encode(`${JSON.stringify(event)}\n`);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `/exec`:NDJSON 流式响应。响应对象立即返回(`readable` 端交给 `Response`),
|
|
199
|
+
* 真正跑命令的工作在一个不 await 的后台 IIFE 里进行,通过 `TransformStream` 的
|
|
200
|
+
* `writable` 端把 N 个 output 事件 + 1 个终块 exit 事件逐行写入——客户端读到
|
|
201
|
+
* 流结束(`writer.close()`)即视为命令完成。
|
|
202
|
+
*/
|
|
203
|
+
async function handleExec(sandbox, body, requestSignal) {
|
|
204
|
+
let sandboxCwd;
|
|
205
|
+
if (body.cwd !== void 0) {
|
|
206
|
+
const normalized = normalizeOrBadRequest(body.cwd);
|
|
207
|
+
if (!normalized.ok) return normalized.response;
|
|
208
|
+
sandboxCwd = toSandboxPath(normalized.path);
|
|
209
|
+
}
|
|
210
|
+
const { readable, writable } = new TransformStream();
|
|
211
|
+
const writer = writable.getWriter();
|
|
212
|
+
const writeEvent = (event) => {
|
|
213
|
+
writer.write(ndjsonLine(event)).catch(() => {});
|
|
214
|
+
};
|
|
215
|
+
(async () => {
|
|
216
|
+
const start = Date.now();
|
|
217
|
+
try {
|
|
218
|
+
const result = await sandbox.exec(body.command, {
|
|
219
|
+
cwd: sandboxCwd,
|
|
220
|
+
timeout: body.timeoutMs,
|
|
221
|
+
signal: requestSignal,
|
|
222
|
+
stream: true,
|
|
223
|
+
onOutput: (stream, data) => writeEvent({
|
|
224
|
+
type: "output",
|
|
225
|
+
stream,
|
|
226
|
+
data
|
|
227
|
+
})
|
|
228
|
+
});
|
|
229
|
+
const exitEvent = require_protocol.execExitEventSchema.parse({
|
|
230
|
+
type: "exit",
|
|
231
|
+
exitCode: result.exitCode,
|
|
232
|
+
stdout: result.stdout,
|
|
233
|
+
stderr: result.stderr,
|
|
234
|
+
durationMs: result.duration
|
|
235
|
+
});
|
|
236
|
+
writeEvent(exitEvent);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
writeEvent({
|
|
239
|
+
type: "exit",
|
|
240
|
+
exitCode: 1,
|
|
241
|
+
stdout: "",
|
|
242
|
+
stderr: `sandbox exec failed: ${describeError(error)}`,
|
|
243
|
+
durationMs: Date.now() - start
|
|
244
|
+
});
|
|
245
|
+
} finally {
|
|
246
|
+
await writer.close().catch(() => {});
|
|
247
|
+
}
|
|
248
|
+
})();
|
|
249
|
+
return new Response(readable, {
|
|
250
|
+
status: 200,
|
|
251
|
+
headers: { "content-type": require_protocol.NDJSON_CONTENT_TYPE }
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function createSandboxGateway(opts) {
|
|
255
|
+
return { async fetch(request) {
|
|
256
|
+
if (request.headers.get("authorization") !== `Bearer ${opts.token}`) return errorResponse(401, "unauthorized", "missing or invalid bearer token");
|
|
257
|
+
const sandboxId = request.headers.get("x-runko-sandbox") ?? "default";
|
|
258
|
+
let sandbox;
|
|
259
|
+
try {
|
|
260
|
+
sandbox = await opts.getSandbox(sandboxId);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return errorResponse(500, "sandbox_error", `getSandbox("${sandboxId}") failed: ${describeError(error)}`);
|
|
263
|
+
}
|
|
264
|
+
const pathname = new URL(request.url).pathname;
|
|
265
|
+
switch (pathname) {
|
|
266
|
+
case require_protocol.ENDPOINTS.read: return withValidatedBody(request, require_protocol.readRequestSchema, (body) => withNormalizedPath(body, (path) => handleRead(sandbox, path)));
|
|
267
|
+
case require_protocol.ENDPOINTS.write: return withValidatedBody(request, require_protocol.writeRequestSchema, (body) => withNormalizedPath(body, (path, b) => handleWrite(sandbox, path, b.dataBase64)));
|
|
268
|
+
case require_protocol.ENDPOINTS.rm: return withValidatedBody(request, require_protocol.rmRequestSchema, (body) => withNormalizedPath(body, (path, b) => handleRm(sandbox, path, b.recursive)));
|
|
269
|
+
case require_protocol.ENDPOINTS.mkdir: return withValidatedBody(request, require_protocol.mkdirRequestSchema, (body) => withNormalizedPath(body, (path) => handleMkdir(sandbox, path)));
|
|
270
|
+
case require_protocol.ENDPOINTS.readdir: return withValidatedBody(request, require_protocol.readdirRequestSchema, (body) => withNormalizedPath(body, (path) => handleReaddir(sandbox, path)));
|
|
271
|
+
case require_protocol.ENDPOINTS.stat: return withValidatedBody(request, require_protocol.statRequestSchema, (body) => withNormalizedPath(body, (path) => handleStat(sandbox, path)));
|
|
272
|
+
case require_protocol.ENDPOINTS.glob: return withValidatedBody(request, require_protocol.globRequestSchema, (body) => handleGlob(sandbox, body.pattern));
|
|
273
|
+
case require_protocol.ENDPOINTS.exec: return withValidatedBody(request, require_protocol.execRequestSchema, (body) => handleExec(sandbox, body, request.signal));
|
|
274
|
+
default: return errorResponse(404, "not_found", `unknown endpoint: ${pathname}`);
|
|
275
|
+
}
|
|
276
|
+
} };
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
exports.createSandboxGateway = createSandboxGateway;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region src/worker.d.ts
|
|
2
|
+
interface CfExecOptions {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
stream?: boolean;
|
|
7
|
+
onOutput?: (stream: "stdout" | "stderr", data: string) => void;
|
|
8
|
+
}
|
|
9
|
+
interface CfExecResult {
|
|
10
|
+
exitCode: number;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
/** 毫秒——真实 SDK 字段名是 `duration`(非 `durationMs`),网关翻译时改名对齐协议。 */
|
|
14
|
+
duration: number;
|
|
15
|
+
}
|
|
16
|
+
interface CfReadFileResult {
|
|
17
|
+
content: string;
|
|
18
|
+
}
|
|
19
|
+
interface CfWriteFileResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface CfMkdirResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface CfDeleteFileResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
}
|
|
28
|
+
type CfFileInfoType = "file" | "directory" | "symlink" | "other";
|
|
29
|
+
interface CfFileInfo {
|
|
30
|
+
name: string;
|
|
31
|
+
/** 相对 `listFiles()` 调用的 `path` 参数;glob 端点据此拼回虚拟绝对路径。 */
|
|
32
|
+
relativePath: string;
|
|
33
|
+
type: CfFileInfoType;
|
|
34
|
+
size: number;
|
|
35
|
+
/** ISO 8601——网关翻译成 epoch ms 填 `StatResponse.mtime`。 */
|
|
36
|
+
modifiedAt: string;
|
|
37
|
+
}
|
|
38
|
+
interface CfListFilesResult {
|
|
39
|
+
files: CfFileInfo[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `@cloudflare/sandbox` 0.12.3 的 `ISandbox` 接口的结构化最小子集——只收本网关
|
|
43
|
+
* 实际调用的方法面(`exec`/`readFile`/`writeFile`/`mkdir`/`deleteFile`/`listFiles`)。
|
|
44
|
+
*/
|
|
45
|
+
interface CfSandboxLike {
|
|
46
|
+
exec(command: string, options?: CfExecOptions): Promise<CfExecResult>;
|
|
47
|
+
readFile(path: string, options?: {
|
|
48
|
+
encoding?: "utf-8" | "base64";
|
|
49
|
+
}): Promise<CfReadFileResult>;
|
|
50
|
+
writeFile(path: string, content: string, options?: {
|
|
51
|
+
encoding?: "utf-8" | "base64";
|
|
52
|
+
}): Promise<CfWriteFileResult>;
|
|
53
|
+
mkdir(path: string, options?: {
|
|
54
|
+
recursive?: boolean;
|
|
55
|
+
}): Promise<CfMkdirResult>;
|
|
56
|
+
deleteFile(path: string): Promise<CfDeleteFileResult>;
|
|
57
|
+
listFiles(path: string, options?: {
|
|
58
|
+
recursive?: boolean;
|
|
59
|
+
}): Promise<CfListFilesResult>;
|
|
60
|
+
}
|
|
61
|
+
interface SandboxGatewayOptions {
|
|
62
|
+
token: string;
|
|
63
|
+
getSandbox: (sandboxId: string) => CfSandboxLike | Promise<CfSandboxLike>;
|
|
64
|
+
}
|
|
65
|
+
interface SandboxGateway {
|
|
66
|
+
fetch(request: Request): Promise<Response>;
|
|
67
|
+
}
|
|
68
|
+
declare function createSandboxGateway(opts: SandboxGatewayOptions): SandboxGateway;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { CfDeleteFileResult, CfExecOptions, CfExecResult, CfFileInfo, CfFileInfoType, CfListFilesResult, CfMkdirResult, CfReadFileResult, CfSandboxLike, CfWriteFileResult, SandboxGateway, SandboxGatewayOptions, createSandboxGateway };
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
//#region src/worker.d.ts
|
|
2
|
+
interface CfExecOptions {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
stream?: boolean;
|
|
7
|
+
onOutput?: (stream: "stdout" | "stderr", data: string) => void;
|
|
8
|
+
}
|
|
9
|
+
interface CfExecResult {
|
|
10
|
+
exitCode: number;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
/** 毫秒——真实 SDK 字段名是 `duration`(非 `durationMs`),网关翻译时改名对齐协议。 */
|
|
14
|
+
duration: number;
|
|
15
|
+
}
|
|
16
|
+
interface CfReadFileResult {
|
|
17
|
+
content: string;
|
|
18
|
+
}
|
|
19
|
+
interface CfWriteFileResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
}
|
|
22
|
+
interface CfMkdirResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface CfDeleteFileResult {
|
|
26
|
+
success: boolean;
|
|
27
|
+
}
|
|
28
|
+
type CfFileInfoType = "file" | "directory" | "symlink" | "other";
|
|
29
|
+
interface CfFileInfo {
|
|
30
|
+
name: string;
|
|
31
|
+
/** 相对 `listFiles()` 调用的 `path` 参数;glob 端点据此拼回虚拟绝对路径。 */
|
|
32
|
+
relativePath: string;
|
|
33
|
+
type: CfFileInfoType;
|
|
34
|
+
size: number;
|
|
35
|
+
/** ISO 8601——网关翻译成 epoch ms 填 `StatResponse.mtime`。 */
|
|
36
|
+
modifiedAt: string;
|
|
37
|
+
}
|
|
38
|
+
interface CfListFilesResult {
|
|
39
|
+
files: CfFileInfo[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `@cloudflare/sandbox` 0.12.3 的 `ISandbox` 接口的结构化最小子集——只收本网关
|
|
43
|
+
* 实际调用的方法面(`exec`/`readFile`/`writeFile`/`mkdir`/`deleteFile`/`listFiles`)。
|
|
44
|
+
*/
|
|
45
|
+
interface CfSandboxLike {
|
|
46
|
+
exec(command: string, options?: CfExecOptions): Promise<CfExecResult>;
|
|
47
|
+
readFile(path: string, options?: {
|
|
48
|
+
encoding?: "utf-8" | "base64";
|
|
49
|
+
}): Promise<CfReadFileResult>;
|
|
50
|
+
writeFile(path: string, content: string, options?: {
|
|
51
|
+
encoding?: "utf-8" | "base64";
|
|
52
|
+
}): Promise<CfWriteFileResult>;
|
|
53
|
+
mkdir(path: string, options?: {
|
|
54
|
+
recursive?: boolean;
|
|
55
|
+
}): Promise<CfMkdirResult>;
|
|
56
|
+
deleteFile(path: string): Promise<CfDeleteFileResult>;
|
|
57
|
+
listFiles(path: string, options?: {
|
|
58
|
+
recursive?: boolean;
|
|
59
|
+
}): Promise<CfListFilesResult>;
|
|
60
|
+
}
|
|
61
|
+
interface SandboxGatewayOptions {
|
|
62
|
+
token: string;
|
|
63
|
+
getSandbox: (sandboxId: string) => CfSandboxLike | Promise<CfSandboxLike>;
|
|
64
|
+
}
|
|
65
|
+
interface SandboxGateway {
|
|
66
|
+
fetch(request: Request): Promise<Response>;
|
|
67
|
+
}
|
|
68
|
+
declare function createSandboxGateway(opts: SandboxGatewayOptions): SandboxGateway;
|
|
69
|
+
//#endregion
|
|
70
|
+
export { CfDeleteFileResult, CfExecOptions, CfExecResult, CfFileInfo, CfFileInfoType, CfListFilesResult, CfMkdirResult, CfReadFileResult, CfSandboxLike, CfWriteFileResult, SandboxGateway, SandboxGatewayOptions, createSandboxGateway };
|