boxer-sdk 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 +228 -0
- package/dist/client-DpDp9i-R.d.cts +39 -0
- package/dist/client-DpDp9i-R.d.ts +39 -0
- package/dist/index.cjs +169 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -0
- package/dist/node.cjs +62 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +16 -0
- package/dist/node.d.ts +16 -0
- package/dist/node.js +37 -0
- package/dist/node.js.map +1 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# boxer-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript client SDK for [Boxer](https://github.com/theonekeyg/boxer) - a sandboxed container execution service backed by gVisor.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install boxer-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+ (or any runtime with native `fetch`) and a running Boxer server.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { BoxerClient } from "boxer-sdk";
|
|
17
|
+
|
|
18
|
+
const client = new BoxerClient({ baseUrl: "http://localhost:8080" });
|
|
19
|
+
|
|
20
|
+
const result = await client.run(
|
|
21
|
+
"python:3.12-slim",
|
|
22
|
+
["python3", "-c", "print('hello world')"],
|
|
23
|
+
);
|
|
24
|
+
console.log(result.stdout); // hello world
|
|
25
|
+
console.log(result.exitCode); // 0
|
|
26
|
+
console.log(result.wall_ms); // e.g. 312
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Hello world in Python, Node.js, and Perl
|
|
32
|
+
|
|
33
|
+
### Python
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const result = await client.run(
|
|
37
|
+
"python:3.12-slim",
|
|
38
|
+
["python3", "-c", "print('hello world')"],
|
|
39
|
+
);
|
|
40
|
+
console.log(result.stdout); // hello world
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Node.js
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const result = await client.run(
|
|
47
|
+
"node:20-slim",
|
|
48
|
+
["node", "-e", "console.log('hello world')"],
|
|
49
|
+
);
|
|
50
|
+
console.log(result.stdout); // hello world
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Perl
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const result = await client.run(
|
|
57
|
+
"perl:5.38-slim",
|
|
58
|
+
["perl", "-e", "print 'hello world\n'"],
|
|
59
|
+
);
|
|
60
|
+
console.log(result.stdout); // hello world
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Working with files
|
|
66
|
+
|
|
67
|
+
### Upload a script and run it
|
|
68
|
+
|
|
69
|
+
Upload a file to the Boxer file store, then reference it by path in `run`.
|
|
70
|
+
The file is bind-mounted read-only at `/<remotePath>` inside the container.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { readFile } from "node:fs/promises";
|
|
74
|
+
|
|
75
|
+
// Python
|
|
76
|
+
const pyScript = await readFile("script.py");
|
|
77
|
+
await client.uploadFile("script.py", pyScript);
|
|
78
|
+
|
|
79
|
+
const result = await client.run(
|
|
80
|
+
"python:3.12-slim",
|
|
81
|
+
["python3", "/script.py"],
|
|
82
|
+
{ files: ["script.py"] },
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Node.js
|
|
86
|
+
const jsScript = await readFile("app.js");
|
|
87
|
+
await client.uploadFile("app.js", jsScript);
|
|
88
|
+
|
|
89
|
+
const result = await client.run(
|
|
90
|
+
"node:20-slim",
|
|
91
|
+
["node", "/app.js"],
|
|
92
|
+
{ files: ["app.js"] },
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Perl
|
|
96
|
+
const plScript = await readFile("hello.pl");
|
|
97
|
+
await client.uploadFile("hello.pl", plScript);
|
|
98
|
+
|
|
99
|
+
const result = await client.run(
|
|
100
|
+
"perl:5.38-slim",
|
|
101
|
+
["perl", "/hello.pl"],
|
|
102
|
+
{ files: ["hello.pl"] },
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Upload a local file or directory
|
|
107
|
+
|
|
108
|
+
`uploadPath` (available from `boxer-sdk/node`) recursively uploads a local file
|
|
109
|
+
or directory, preserving the directory structure.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { uploadPath } from "boxer-sdk/node";
|
|
113
|
+
|
|
114
|
+
// Upload a single file
|
|
115
|
+
const paths = await uploadPath(client, "./script.py");
|
|
116
|
+
// => ["script.py"]
|
|
117
|
+
|
|
118
|
+
// Upload an entire directory
|
|
119
|
+
const paths = await uploadPath(client, "./myproject", "myproject");
|
|
120
|
+
// => ["myproject/main.py", "myproject/utils.py", ...]
|
|
121
|
+
|
|
122
|
+
const result = await client.run(
|
|
123
|
+
"python:3.12-slim",
|
|
124
|
+
["python3", "/myproject/main.py"],
|
|
125
|
+
{ files: paths },
|
|
126
|
+
);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Download output files from the container
|
|
130
|
+
|
|
131
|
+
Any file the container writes to `/output/` is automatically captured and
|
|
132
|
+
retrievable via `downloadFile` after the run completes.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const code = `
|
|
136
|
+
import os, json
|
|
137
|
+
os.makedirs('/output', exist_ok=True)
|
|
138
|
+
with open('/output/result.json', 'w') as f:
|
|
139
|
+
json.dump({'message': 'hello world', 'value': 42}, f)
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
const script = new TextEncoder().encode(code);
|
|
143
|
+
await client.uploadFile("compute.py", script);
|
|
144
|
+
|
|
145
|
+
const result = await client.run(
|
|
146
|
+
"python:3.12-slim",
|
|
147
|
+
["python3", "/compute.py"],
|
|
148
|
+
{ files: ["compute.py"] },
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Download the file the container wrote
|
|
152
|
+
const data = await client.downloadFile(`output/${result.exec_id}/result.json`);
|
|
153
|
+
console.log(new TextDecoder().decode(data)); // {"message": "hello world", "value": 42}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The output path pattern is always `output/<exec_id>/<filename>`.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Resource limits
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import type { ResourceLimits } from "boxer-sdk";
|
|
164
|
+
|
|
165
|
+
const limits: ResourceLimits = {
|
|
166
|
+
cpu_cores: 0.5,
|
|
167
|
+
memory_mb: 128,
|
|
168
|
+
wall_clock_secs: 10,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const result = await client.run(
|
|
172
|
+
"python:3.12-slim",
|
|
173
|
+
["python3", "-c", "print('done')"],
|
|
174
|
+
{ limits },
|
|
175
|
+
);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Error handling
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { BoxerAPIError, BoxerTimeoutError, BoxerOutputLimitError } from "boxer-sdk";
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const result = await client.run(
|
|
187
|
+
"python:3.12-slim",
|
|
188
|
+
["python3", "-c", "while True: pass"],
|
|
189
|
+
{ limits: { wall_clock_secs: 5 } },
|
|
190
|
+
);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
if (err instanceof BoxerTimeoutError) {
|
|
193
|
+
console.error("execution timed out");
|
|
194
|
+
} else if (err instanceof BoxerOutputLimitError) {
|
|
195
|
+
console.error("output exceeded size limit");
|
|
196
|
+
} else if (err instanceof BoxerAPIError) {
|
|
197
|
+
console.error(`API error ${err.statusCode}: ${err.message}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Runtime compatibility
|
|
205
|
+
|
|
206
|
+
The core `boxer-sdk` package uses only web-standard APIs (`fetch`, `FormData`,
|
|
207
|
+
`Blob`) and has zero dependencies, making it compatible with:
|
|
208
|
+
|
|
209
|
+
- Node.js 18+
|
|
210
|
+
- Bun
|
|
211
|
+
- Deno
|
|
212
|
+
- Browsers
|
|
213
|
+
|
|
214
|
+
The `boxer-sdk/node` subpath export (`uploadPath`) requires a Node.js-compatible
|
|
215
|
+
`fs` module and is available in Node, Bun, and Deno.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Running tests
|
|
220
|
+
|
|
221
|
+
Tests require a live Boxer server:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
pnpm install
|
|
225
|
+
BOXER_URL=http://localhost:8080 pnpm test
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Without `BOXER_URL` all tests are skipped automatically.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface ResourceLimits {
|
|
2
|
+
cpu_cores?: number;
|
|
3
|
+
memory_mb?: number;
|
|
4
|
+
pids_limit?: number;
|
|
5
|
+
wall_clock_secs?: number;
|
|
6
|
+
nofile?: number;
|
|
7
|
+
}
|
|
8
|
+
interface RunResult {
|
|
9
|
+
exec_id: string;
|
|
10
|
+
exit_code: number;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
wall_ms: number;
|
|
14
|
+
}
|
|
15
|
+
interface RunOptions {
|
|
16
|
+
env?: string[];
|
|
17
|
+
cwd?: string;
|
|
18
|
+
limits?: ResourceLimits;
|
|
19
|
+
files?: string[];
|
|
20
|
+
persist?: boolean;
|
|
21
|
+
network?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface BoxerClientOptions {
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
timeout?: number;
|
|
27
|
+
}
|
|
28
|
+
declare class BoxerClient {
|
|
29
|
+
private readonly baseUrl;
|
|
30
|
+
private readonly timeout;
|
|
31
|
+
constructor(options?: BoxerClientOptions);
|
|
32
|
+
private fetch;
|
|
33
|
+
health(): Promise<boolean>;
|
|
34
|
+
run(image: string, cmd: string[], options?: RunOptions): Promise<RunResult>;
|
|
35
|
+
uploadFile(remotePath: string, content: Blob | Uint8Array | ArrayBuffer): Promise<void>;
|
|
36
|
+
downloadFile(path: string): Promise<Uint8Array>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { BoxerClient as B, type ResourceLimits as R, type BoxerClientOptions as a, type RunOptions as b, type RunResult as c };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
interface ResourceLimits {
|
|
2
|
+
cpu_cores?: number;
|
|
3
|
+
memory_mb?: number;
|
|
4
|
+
pids_limit?: number;
|
|
5
|
+
wall_clock_secs?: number;
|
|
6
|
+
nofile?: number;
|
|
7
|
+
}
|
|
8
|
+
interface RunResult {
|
|
9
|
+
exec_id: string;
|
|
10
|
+
exit_code: number;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
wall_ms: number;
|
|
14
|
+
}
|
|
15
|
+
interface RunOptions {
|
|
16
|
+
env?: string[];
|
|
17
|
+
cwd?: string;
|
|
18
|
+
limits?: ResourceLimits;
|
|
19
|
+
files?: string[];
|
|
20
|
+
persist?: boolean;
|
|
21
|
+
network?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface BoxerClientOptions {
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
timeout?: number;
|
|
27
|
+
}
|
|
28
|
+
declare class BoxerClient {
|
|
29
|
+
private readonly baseUrl;
|
|
30
|
+
private readonly timeout;
|
|
31
|
+
constructor(options?: BoxerClientOptions);
|
|
32
|
+
private fetch;
|
|
33
|
+
health(): Promise<boolean>;
|
|
34
|
+
run(image: string, cmd: string[], options?: RunOptions): Promise<RunResult>;
|
|
35
|
+
uploadFile(remotePath: string, content: Blob | Uint8Array | ArrayBuffer): Promise<void>;
|
|
36
|
+
downloadFile(path: string): Promise<Uint8Array>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { BoxerClient as B, type ResourceLimits as R, type BoxerClientOptions as a, type RunOptions as b, type RunResult as c };
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
BoxerAPIError: () => BoxerAPIError,
|
|
24
|
+
BoxerClient: () => BoxerClient,
|
|
25
|
+
BoxerError: () => BoxerError,
|
|
26
|
+
BoxerOutputLimitError: () => BoxerOutputLimitError,
|
|
27
|
+
BoxerTimeoutError: () => BoxerTimeoutError
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(src_exports);
|
|
30
|
+
|
|
31
|
+
// src/errors.ts
|
|
32
|
+
var BoxerError = class extends Error {
|
|
33
|
+
constructor(message) {
|
|
34
|
+
super(message);
|
|
35
|
+
this.name = "BoxerError";
|
|
36
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var BoxerAPIError = class extends BoxerError {
|
|
40
|
+
constructor(message, statusCode) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.statusCode = statusCode;
|
|
43
|
+
this.name = "BoxerAPIError";
|
|
44
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var BoxerTimeoutError = class extends BoxerAPIError {
|
|
48
|
+
constructor(message, statusCode) {
|
|
49
|
+
super(message, statusCode);
|
|
50
|
+
this.name = "BoxerTimeoutError";
|
|
51
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var BoxerOutputLimitError = class extends BoxerAPIError {
|
|
55
|
+
constructor(message, statusCode) {
|
|
56
|
+
super(message, statusCode);
|
|
57
|
+
this.name = "BoxerOutputLimitError";
|
|
58
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/client.ts
|
|
63
|
+
async function raiseForStatus(res) {
|
|
64
|
+
if (res.ok) return;
|
|
65
|
+
let detail;
|
|
66
|
+
try {
|
|
67
|
+
const body = await res.json();
|
|
68
|
+
detail = typeof body.error === "string" ? body.error : res.statusText;
|
|
69
|
+
} catch {
|
|
70
|
+
detail = res.statusText;
|
|
71
|
+
}
|
|
72
|
+
const code = res.status;
|
|
73
|
+
if (code === 408) throw new BoxerTimeoutError(detail, code);
|
|
74
|
+
if (code === 507) throw new BoxerOutputLimitError(detail, code);
|
|
75
|
+
throw new BoxerAPIError(detail, code);
|
|
76
|
+
}
|
|
77
|
+
function buildRunBody(image, cmd, options) {
|
|
78
|
+
const body = { image, cmd };
|
|
79
|
+
if (options.env?.length) body.env = options.env;
|
|
80
|
+
if (options.cwd && options.cwd !== "/") body.cwd = options.cwd;
|
|
81
|
+
if (options.limits) body.limits = limitsToObject(options.limits);
|
|
82
|
+
if (options.files?.length) body.files = options.files;
|
|
83
|
+
if (options.persist) body.persist = options.persist;
|
|
84
|
+
if (options.network != null) body.network = options.network;
|
|
85
|
+
return body;
|
|
86
|
+
}
|
|
87
|
+
function limitsToObject(limits) {
|
|
88
|
+
const out = {};
|
|
89
|
+
if (limits.cpu_cores != null) out.cpu_cores = limits.cpu_cores;
|
|
90
|
+
if (limits.memory_mb != null) out.memory_mb = limits.memory_mb;
|
|
91
|
+
if (limits.pids_limit != null) out.pids_limit = limits.pids_limit;
|
|
92
|
+
if (limits.wall_clock_secs != null) out.wall_clock_secs = limits.wall_clock_secs;
|
|
93
|
+
if (limits.nofile != null) out.nofile = limits.nofile;
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
function parseRunResult(data) {
|
|
97
|
+
if (data == null || typeof data !== "object" || !("exec_id" in data) || !("exit_code" in data) || !("stdout" in data) || !("stderr" in data) || !("wall_ms" in data)) {
|
|
98
|
+
throw new Error("Unexpected response from Boxer API: missing required fields");
|
|
99
|
+
}
|
|
100
|
+
const d = data;
|
|
101
|
+
return {
|
|
102
|
+
exec_id: String(d.exec_id),
|
|
103
|
+
exit_code: Number(d.exit_code),
|
|
104
|
+
stdout: String(d.stdout),
|
|
105
|
+
stderr: String(d.stderr),
|
|
106
|
+
wall_ms: Number(d.wall_ms)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
var BoxerClient = class {
|
|
110
|
+
constructor(options = {}) {
|
|
111
|
+
this.baseUrl = (options.baseUrl ?? "http://localhost:8080").replace(/\/$/, "");
|
|
112
|
+
this.timeout = options.timeout ?? 12e4;
|
|
113
|
+
}
|
|
114
|
+
async fetch(path, init = {}) {
|
|
115
|
+
const controller = new AbortController();
|
|
116
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
117
|
+
try {
|
|
118
|
+
return await fetch(`${this.baseUrl}${path}`, {
|
|
119
|
+
...init,
|
|
120
|
+
signal: controller.signal
|
|
121
|
+
});
|
|
122
|
+
} finally {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async health() {
|
|
127
|
+
const res = await this.fetch("/healthz");
|
|
128
|
+
return res.ok;
|
|
129
|
+
}
|
|
130
|
+
async run(image, cmd, options = {}) {
|
|
131
|
+
const body = buildRunBody(image, cmd, options);
|
|
132
|
+
const res = await this.fetch("/run", {
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: { "Content-Type": "application/json" },
|
|
135
|
+
body: JSON.stringify(body)
|
|
136
|
+
});
|
|
137
|
+
await raiseForStatus(res);
|
|
138
|
+
return parseRunResult(await res.json());
|
|
139
|
+
}
|
|
140
|
+
async uploadFile(remotePath, content) {
|
|
141
|
+
const form = new FormData();
|
|
142
|
+
form.append("path", remotePath);
|
|
143
|
+
let blob;
|
|
144
|
+
if (content instanceof Blob) {
|
|
145
|
+
blob = content;
|
|
146
|
+
} else if (content instanceof Uint8Array) {
|
|
147
|
+
blob = new Blob([new Uint8Array(content)], { type: "application/octet-stream" });
|
|
148
|
+
} else {
|
|
149
|
+
blob = new Blob([content], { type: "application/octet-stream" });
|
|
150
|
+
}
|
|
151
|
+
form.append("file", blob, remotePath.split("/").pop() ?? "file");
|
|
152
|
+
const res = await this.fetch("/files", { method: "POST", body: form });
|
|
153
|
+
await raiseForStatus(res);
|
|
154
|
+
}
|
|
155
|
+
async downloadFile(path) {
|
|
156
|
+
const res = await this.fetch(`/files?${new URLSearchParams({ path }).toString()}`);
|
|
157
|
+
await raiseForStatus(res);
|
|
158
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
162
|
+
0 && (module.exports = {
|
|
163
|
+
BoxerAPIError,
|
|
164
|
+
BoxerClient,
|
|
165
|
+
BoxerError,
|
|
166
|
+
BoxerOutputLimitError,
|
|
167
|
+
BoxerTimeoutError
|
|
168
|
+
});
|
|
169
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { BoxerClient } from \"./client.js\";\nexport type { BoxerClientOptions } from \"./client.js\";\nexport { BoxerAPIError, BoxerError, BoxerOutputLimitError, BoxerTimeoutError } from \"./errors.js\";\nexport type { ResourceLimits, RunOptions, RunResult } from \"./types.js\";\n","export class BoxerError extends Error {\n override name = \"BoxerError\";\n\n constructor(message: string) {\n super(message);\n // Restore prototype chain for instanceof checks across transpile boundaries\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerAPIError extends BoxerError {\n override name = \"BoxerAPIError\";\n\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerTimeoutError extends BoxerAPIError {\n override name = \"BoxerTimeoutError\";\n\n constructor(message: string, statusCode: number) {\n super(message, statusCode);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerOutputLimitError extends BoxerAPIError {\n override name = \"BoxerOutputLimitError\";\n\n constructor(message: string, statusCode: number) {\n super(message, statusCode);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { BoxerAPIError, BoxerOutputLimitError, BoxerTimeoutError } from \"./errors.js\";\nimport type { ResourceLimits, RunOptions, RunResult } from \"./types.js\";\n\nexport interface BoxerClientOptions {\n baseUrl?: string;\n timeout?: number;\n}\n\nasync function raiseForStatus(res: Response): Promise<void> {\n if (res.ok) return;\n\n let detail: string;\n try {\n const body = (await res.json()) as Record<string, unknown>;\n detail = typeof body.error === \"string\" ? body.error : res.statusText;\n } catch {\n detail = res.statusText;\n }\n\n const code = res.status;\n if (code === 408) throw new BoxerTimeoutError(detail, code);\n if (code === 507) throw new BoxerOutputLimitError(detail, code);\n throw new BoxerAPIError(detail, code);\n}\n\nfunction buildRunBody(image: string, cmd: string[], options: RunOptions): Record<string, unknown> {\n const body: Record<string, unknown> = { image, cmd };\n if (options.env?.length) body.env = options.env;\n if (options.cwd && options.cwd !== \"/\") body.cwd = options.cwd;\n if (options.limits) body.limits = limitsToObject(options.limits);\n if (options.files?.length) body.files = options.files;\n if (options.persist) body.persist = options.persist;\n if (options.network != null) body.network = options.network;\n return body;\n}\n\nfunction limitsToObject(limits: ResourceLimits): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (limits.cpu_cores != null) out.cpu_cores = limits.cpu_cores;\n if (limits.memory_mb != null) out.memory_mb = limits.memory_mb;\n if (limits.pids_limit != null) out.pids_limit = limits.pids_limit;\n if (limits.wall_clock_secs != null) out.wall_clock_secs = limits.wall_clock_secs;\n if (limits.nofile != null) out.nofile = limits.nofile;\n return out;\n}\n\nfunction parseRunResult(data: unknown): RunResult {\n if (\n data == null ||\n typeof data !== \"object\" ||\n !(\"exec_id\" in data) ||\n !(\"exit_code\" in data) ||\n !(\"stdout\" in data) ||\n !(\"stderr\" in data) ||\n !(\"wall_ms\" in data)\n ) {\n throw new Error(\"Unexpected response from Boxer API: missing required fields\");\n }\n\n const d = data as Record<string, unknown>;\n return {\n exec_id: String(d.exec_id),\n exit_code: Number(d.exit_code),\n stdout: String(d.stdout),\n stderr: String(d.stderr),\n wall_ms: Number(d.wall_ms),\n };\n}\n\nexport class BoxerClient {\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(options: BoxerClientOptions = {}) {\n this.baseUrl = (options.baseUrl ?? \"http://localhost:8080\").replace(/\\/$/, \"\");\n this.timeout = options.timeout ?? 120_000;\n }\n\n private async fetch(path: string, init: RequestInit = {}): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n return await fetch(`${this.baseUrl}${path}`, {\n ...init,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n async health(): Promise<boolean> {\n const res = await this.fetch(\"/healthz\");\n return res.ok;\n }\n\n async run(image: string, cmd: string[], options: RunOptions = {}): Promise<RunResult> {\n const body = buildRunBody(image, cmd, options);\n const res = await this.fetch(\"/run\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n await raiseForStatus(res);\n return parseRunResult(await res.json());\n }\n\n async uploadFile(remotePath: string, content: Blob | Uint8Array | ArrayBuffer): Promise<void> {\n const form = new FormData();\n form.append(\"path\", remotePath);\n let blob: Blob;\n if (content instanceof Blob) {\n blob = content;\n } else if (content instanceof Uint8Array) {\n // Copy via ArrayLike<number> to ensure ArrayBuffer (not SharedArrayBuffer)\n blob = new Blob([new Uint8Array(content)], { type: \"application/octet-stream\" });\n } else {\n blob = new Blob([content], { type: \"application/octet-stream\" });\n }\n form.append(\"file\", blob, remotePath.split(\"/\").pop() ?? \"file\");\n\n const res = await this.fetch(\"/files\", { method: \"POST\", body: form });\n await raiseForStatus(res);\n }\n\n async downloadFile(path: string): Promise<Uint8Array> {\n const res = await this.fetch(`/files?${new URLSearchParams({ path }).toString()}`);\n await raiseForStatus(res);\n return new Uint8Array(await res.arrayBuffer());\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,aAAN,cAAyB,MAAM;AAAA,EAGpC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AAHf,SAAS,OAAO;AAKd,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;AAEO,IAAM,gBAAN,cAA4B,WAAW;AAAA,EAG5C,YACE,SACgB,YAChB;AACA,UAAM,OAAO;AAFG;AAJlB,SAAS,OAAO;AAOd,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;AAEO,IAAM,oBAAN,cAAgC,cAAc;AAAA,EAGnD,YAAY,SAAiB,YAAoB;AAC/C,UAAM,SAAS,UAAU;AAH3B,SAAS,OAAO;AAId,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;AAEO,IAAM,wBAAN,cAAoC,cAAc;AAAA,EAGvD,YAAY,SAAiB,YAAoB;AAC/C,UAAM,SAAS,UAAU;AAH3B,SAAS,OAAO;AAId,WAAO,eAAe,MAAM,WAAW,SAAS;AAAA,EAClD;AACF;;;AC9BA,eAAe,eAAe,KAA8B;AAC1D,MAAI,IAAI,GAAI;AAEZ,MAAI;AACJ,MAAI;AACF,UAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,aAAS,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,IAAI;AAAA,EAC7D,QAAQ;AACN,aAAS,IAAI;AAAA,EACf;AAEA,QAAM,OAAO,IAAI;AACjB,MAAI,SAAS,IAAK,OAAM,IAAI,kBAAkB,QAAQ,IAAI;AAC1D,MAAI,SAAS,IAAK,OAAM,IAAI,sBAAsB,QAAQ,IAAI;AAC9D,QAAM,IAAI,cAAc,QAAQ,IAAI;AACtC;AAEA,SAAS,aAAa,OAAe,KAAe,SAA8C;AAChG,QAAM,OAAgC,EAAE,OAAO,IAAI;AACnD,MAAI,QAAQ,KAAK,OAAQ,MAAK,MAAM,QAAQ;AAC5C,MAAI,QAAQ,OAAO,QAAQ,QAAQ,IAAK,MAAK,MAAM,QAAQ;AAC3D,MAAI,QAAQ,OAAQ,MAAK,SAAS,eAAe,QAAQ,MAAM;AAC/D,MAAI,QAAQ,OAAO,OAAQ,MAAK,QAAQ,QAAQ;AAChD,MAAI,QAAQ,QAAS,MAAK,UAAU,QAAQ;AAC5C,MAAI,QAAQ,WAAW,KAAM,MAAK,UAAU,QAAQ;AACpD,SAAO;AACT;AAEA,SAAS,eAAe,QAAiD;AACvE,QAAM,MAA+B,CAAC;AACtC,MAAI,OAAO,aAAa,KAAM,KAAI,YAAY,OAAO;AACrD,MAAI,OAAO,aAAa,KAAM,KAAI,YAAY,OAAO;AACrD,MAAI,OAAO,cAAc,KAAM,KAAI,aAAa,OAAO;AACvD,MAAI,OAAO,mBAAmB,KAAM,KAAI,kBAAkB,OAAO;AACjE,MAAI,OAAO,UAAU,KAAM,KAAI,SAAS,OAAO;AAC/C,SAAO;AACT;AAEA,SAAS,eAAe,MAA0B;AAChD,MACE,QAAQ,QACR,OAAO,SAAS,YAChB,EAAE,aAAa,SACf,EAAE,eAAe,SACjB,EAAE,YAAY,SACd,EAAE,YAAY,SACd,EAAE,aAAa,OACf;AACA,UAAM,IAAI,MAAM,6DAA6D;AAAA,EAC/E;AAEA,QAAM,IAAI;AACV,SAAO;AAAA,IACL,SAAS,OAAO,EAAE,OAAO;AAAA,IACzB,WAAW,OAAO,EAAE,SAAS;AAAA,IAC7B,QAAQ,OAAO,EAAE,MAAM;AAAA,IACvB,QAAQ,OAAO,EAAE,MAAM;AAAA,IACvB,SAAS,OAAO,EAAE,OAAO;AAAA,EAC3B;AACF;AAEO,IAAM,cAAN,MAAkB;AAAA,EAIvB,YAAY,UAA8B,CAAC,GAAG;AAC5C,SAAK,WAAW,QAAQ,WAAW,yBAAyB,QAAQ,OAAO,EAAE;AAC7E,SAAK,UAAU,QAAQ,WAAW;AAAA,EACpC;AAAA,EAEA,MAAc,MAAM,MAAc,OAAoB,CAAC,GAAsB;AAC3E,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,KAAK,OAAO;AAE/D,QAAI;AACF,aAAO,MAAM,MAAM,GAAG,KAAK,OAAO,GAAG,IAAI,IAAI;AAAA,QAC3C,GAAG;AAAA,QACH,QAAQ,WAAW;AAAA,MACrB,CAAC;AAAA,IACH,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,MAAM,SAA2B;AAC/B,UAAM,MAAM,MAAM,KAAK,MAAM,UAAU;AACvC,WAAO,IAAI;AAAA,EACb;AAAA,EAEA,MAAM,IAAI,OAAe,KAAe,UAAsB,CAAC,GAAuB;AACpF,UAAM,OAAO,aAAa,OAAO,KAAK,OAAO;AAC7C,UAAM,MAAM,MAAM,KAAK,MAAM,QAAQ;AAAA,MACnC,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AACD,UAAM,eAAe,GAAG;AACxB,WAAO,eAAe,MAAM,IAAI,KAAK,CAAC;AAAA,EACxC;AAAA,EAEA,MAAM,WAAW,YAAoB,SAAyD;AAC5F,UAAM,OAAO,IAAI,SAAS;AAC1B,SAAK,OAAO,QAAQ,UAAU;AAC9B,QAAI;AACJ,QAAI,mBAAmB,MAAM;AAC3B,aAAO;AAAA,IACT,WAAW,mBAAmB,YAAY;AAExC,aAAO,IAAI,KAAK,CAAC,IAAI,WAAW,OAAO,CAAC,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAAA,IACjF,OAAO;AACL,aAAO,IAAI,KAAK,CAAC,OAAO,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAAA,IACjE;AACA,SAAK,OAAO,QAAQ,MAAM,WAAW,MAAM,GAAG,EAAE,IAAI,KAAK,MAAM;AAE/D,UAAM,MAAM,MAAM,KAAK,MAAM,UAAU,EAAE,QAAQ,QAAQ,MAAM,KAAK,CAAC;AACrE,UAAM,eAAe,GAAG;AAAA,EAC1B;AAAA,EAEA,MAAM,aAAa,MAAmC;AACpD,UAAM,MAAM,MAAM,KAAK,MAAM,UAAU,IAAI,gBAAgB,EAAE,KAAK,CAAC,EAAE,SAAS,CAAC,EAAE;AACjF,UAAM,eAAe,GAAG;AACxB,WAAO,IAAI,WAAW,MAAM,IAAI,YAAY,CAAC;AAAA,EAC/C;AACF;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { B as BoxerClient, a as BoxerClientOptions, R as ResourceLimits, b as RunOptions, c as RunResult } from './client-DpDp9i-R.cjs';
|
|
2
|
+
|
|
3
|
+
declare class BoxerError extends Error {
|
|
4
|
+
name: string;
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
declare class BoxerAPIError extends BoxerError {
|
|
8
|
+
readonly statusCode: number;
|
|
9
|
+
name: string;
|
|
10
|
+
constructor(message: string, statusCode: number);
|
|
11
|
+
}
|
|
12
|
+
declare class BoxerTimeoutError extends BoxerAPIError {
|
|
13
|
+
name: string;
|
|
14
|
+
constructor(message: string, statusCode: number);
|
|
15
|
+
}
|
|
16
|
+
declare class BoxerOutputLimitError extends BoxerAPIError {
|
|
17
|
+
name: string;
|
|
18
|
+
constructor(message: string, statusCode: number);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { BoxerAPIError, BoxerError, BoxerOutputLimitError, BoxerTimeoutError };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { B as BoxerClient, a as BoxerClientOptions, R as ResourceLimits, b as RunOptions, c as RunResult } from './client-DpDp9i-R.js';
|
|
2
|
+
|
|
3
|
+
declare class BoxerError extends Error {
|
|
4
|
+
name: string;
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
declare class BoxerAPIError extends BoxerError {
|
|
8
|
+
readonly statusCode: number;
|
|
9
|
+
name: string;
|
|
10
|
+
constructor(message: string, statusCode: number);
|
|
11
|
+
}
|
|
12
|
+
declare class BoxerTimeoutError extends BoxerAPIError {
|
|
13
|
+
name: string;
|
|
14
|
+
constructor(message: string, statusCode: number);
|
|
15
|
+
}
|
|
16
|
+
declare class BoxerOutputLimitError extends BoxerAPIError {
|
|
17
|
+
name: string;
|
|
18
|
+
constructor(message: string, statusCode: number);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { BoxerAPIError, BoxerError, BoxerOutputLimitError, BoxerTimeoutError };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var BoxerError = class extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "BoxerError";
|
|
6
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var BoxerAPIError = class extends BoxerError {
|
|
10
|
+
constructor(message, statusCode) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
this.name = "BoxerAPIError";
|
|
14
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var BoxerTimeoutError = class extends BoxerAPIError {
|
|
18
|
+
constructor(message, statusCode) {
|
|
19
|
+
super(message, statusCode);
|
|
20
|
+
this.name = "BoxerTimeoutError";
|
|
21
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var BoxerOutputLimitError = class extends BoxerAPIError {
|
|
25
|
+
constructor(message, statusCode) {
|
|
26
|
+
super(message, statusCode);
|
|
27
|
+
this.name = "BoxerOutputLimitError";
|
|
28
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// src/client.ts
|
|
33
|
+
async function raiseForStatus(res) {
|
|
34
|
+
if (res.ok) return;
|
|
35
|
+
let detail;
|
|
36
|
+
try {
|
|
37
|
+
const body = await res.json();
|
|
38
|
+
detail = typeof body.error === "string" ? body.error : res.statusText;
|
|
39
|
+
} catch {
|
|
40
|
+
detail = res.statusText;
|
|
41
|
+
}
|
|
42
|
+
const code = res.status;
|
|
43
|
+
if (code === 408) throw new BoxerTimeoutError(detail, code);
|
|
44
|
+
if (code === 507) throw new BoxerOutputLimitError(detail, code);
|
|
45
|
+
throw new BoxerAPIError(detail, code);
|
|
46
|
+
}
|
|
47
|
+
function buildRunBody(image, cmd, options) {
|
|
48
|
+
const body = { image, cmd };
|
|
49
|
+
if (options.env?.length) body.env = options.env;
|
|
50
|
+
if (options.cwd && options.cwd !== "/") body.cwd = options.cwd;
|
|
51
|
+
if (options.limits) body.limits = limitsToObject(options.limits);
|
|
52
|
+
if (options.files?.length) body.files = options.files;
|
|
53
|
+
if (options.persist) body.persist = options.persist;
|
|
54
|
+
if (options.network != null) body.network = options.network;
|
|
55
|
+
return body;
|
|
56
|
+
}
|
|
57
|
+
function limitsToObject(limits) {
|
|
58
|
+
const out = {};
|
|
59
|
+
if (limits.cpu_cores != null) out.cpu_cores = limits.cpu_cores;
|
|
60
|
+
if (limits.memory_mb != null) out.memory_mb = limits.memory_mb;
|
|
61
|
+
if (limits.pids_limit != null) out.pids_limit = limits.pids_limit;
|
|
62
|
+
if (limits.wall_clock_secs != null) out.wall_clock_secs = limits.wall_clock_secs;
|
|
63
|
+
if (limits.nofile != null) out.nofile = limits.nofile;
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function parseRunResult(data) {
|
|
67
|
+
if (data == null || typeof data !== "object" || !("exec_id" in data) || !("exit_code" in data) || !("stdout" in data) || !("stderr" in data) || !("wall_ms" in data)) {
|
|
68
|
+
throw new Error("Unexpected response from Boxer API: missing required fields");
|
|
69
|
+
}
|
|
70
|
+
const d = data;
|
|
71
|
+
return {
|
|
72
|
+
exec_id: String(d.exec_id),
|
|
73
|
+
exit_code: Number(d.exit_code),
|
|
74
|
+
stdout: String(d.stdout),
|
|
75
|
+
stderr: String(d.stderr),
|
|
76
|
+
wall_ms: Number(d.wall_ms)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var BoxerClient = class {
|
|
80
|
+
constructor(options = {}) {
|
|
81
|
+
this.baseUrl = (options.baseUrl ?? "http://localhost:8080").replace(/\/$/, "");
|
|
82
|
+
this.timeout = options.timeout ?? 12e4;
|
|
83
|
+
}
|
|
84
|
+
async fetch(path, init = {}) {
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
87
|
+
try {
|
|
88
|
+
return await fetch(`${this.baseUrl}${path}`, {
|
|
89
|
+
...init,
|
|
90
|
+
signal: controller.signal
|
|
91
|
+
});
|
|
92
|
+
} finally {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async health() {
|
|
97
|
+
const res = await this.fetch("/healthz");
|
|
98
|
+
return res.ok;
|
|
99
|
+
}
|
|
100
|
+
async run(image, cmd, options = {}) {
|
|
101
|
+
const body = buildRunBody(image, cmd, options);
|
|
102
|
+
const res = await this.fetch("/run", {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: { "Content-Type": "application/json" },
|
|
105
|
+
body: JSON.stringify(body)
|
|
106
|
+
});
|
|
107
|
+
await raiseForStatus(res);
|
|
108
|
+
return parseRunResult(await res.json());
|
|
109
|
+
}
|
|
110
|
+
async uploadFile(remotePath, content) {
|
|
111
|
+
const form = new FormData();
|
|
112
|
+
form.append("path", remotePath);
|
|
113
|
+
let blob;
|
|
114
|
+
if (content instanceof Blob) {
|
|
115
|
+
blob = content;
|
|
116
|
+
} else if (content instanceof Uint8Array) {
|
|
117
|
+
blob = new Blob([new Uint8Array(content)], { type: "application/octet-stream" });
|
|
118
|
+
} else {
|
|
119
|
+
blob = new Blob([content], { type: "application/octet-stream" });
|
|
120
|
+
}
|
|
121
|
+
form.append("file", blob, remotePath.split("/").pop() ?? "file");
|
|
122
|
+
const res = await this.fetch("/files", { method: "POST", body: form });
|
|
123
|
+
await raiseForStatus(res);
|
|
124
|
+
}
|
|
125
|
+
async downloadFile(path) {
|
|
126
|
+
const res = await this.fetch(`/files?${new URLSearchParams({ path }).toString()}`);
|
|
127
|
+
await raiseForStatus(res);
|
|
128
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
export {
|
|
132
|
+
BoxerAPIError,
|
|
133
|
+
BoxerClient,
|
|
134
|
+
BoxerError,
|
|
135
|
+
BoxerOutputLimitError,
|
|
136
|
+
BoxerTimeoutError
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["export class BoxerError extends Error {\n override name = \"BoxerError\";\n\n constructor(message: string) {\n super(message);\n // Restore prototype chain for instanceof checks across transpile boundaries\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerAPIError extends BoxerError {\n override name = \"BoxerAPIError\";\n\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerTimeoutError extends BoxerAPIError {\n override name = \"BoxerTimeoutError\";\n\n constructor(message: string, statusCode: number) {\n super(message, statusCode);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport class BoxerOutputLimitError extends BoxerAPIError {\n override name = \"BoxerOutputLimitError\";\n\n constructor(message: string, statusCode: number) {\n super(message, statusCode);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import { BoxerAPIError, BoxerOutputLimitError, BoxerTimeoutError } from \"./errors.js\";\nimport type { ResourceLimits, RunOptions, RunResult } from \"./types.js\";\n\nexport interface BoxerClientOptions {\n baseUrl?: string;\n timeout?: number;\n}\n\nasync function raiseForStatus(res: Response): Promise<void> {\n if (res.ok) return;\n\n let detail: string;\n try {\n const body = (await res.json()) as Record<string, unknown>;\n detail = typeof body.error === \"string\" ? body.error : res.statusText;\n } catch {\n detail = res.statusText;\n }\n\n const code = res.status;\n if (code === 408) throw new BoxerTimeoutError(detail, code);\n if (code === 507) throw new BoxerOutputLimitError(detail, code);\n throw new BoxerAPIError(detail, code);\n}\n\nfunction buildRunBody(image: string, cmd: string[], options: RunOptions): Record<string, unknown> {\n const body: Record<string, unknown> = { image, cmd };\n if (options.env?.length) body.env = options.env;\n if (options.cwd && options.cwd !== \"/\") body.cwd = options.cwd;\n if (options.limits) body.limits = limitsToObject(options.limits);\n if (options.files?.length) body.files = options.files;\n if (options.persist) body.persist = options.persist;\n if (options.network != null) body.network = options.network;\n return body;\n}\n\nfunction limitsToObject(limits: ResourceLimits): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n if (limits.cpu_cores != null) out.cpu_cores = limits.cpu_cores;\n if (limits.memory_mb != null) out.memory_mb = limits.memory_mb;\n if (limits.pids_limit != null) out.pids_limit = limits.pids_limit;\n if (limits.wall_clock_secs != null) out.wall_clock_secs = limits.wall_clock_secs;\n if (limits.nofile != null) out.nofile = limits.nofile;\n return out;\n}\n\nfunction parseRunResult(data: unknown): RunResult {\n if (\n data == null ||\n typeof data !== \"object\" ||\n !(\"exec_id\" in data) ||\n !(\"exit_code\" in data) ||\n !(\"stdout\" in data) ||\n !(\"stderr\" in data) ||\n !(\"wall_ms\" in data)\n ) {\n throw new Error(\"Unexpected response from Boxer API: missing required fields\");\n }\n\n const d = data as Record<string, unknown>;\n return {\n exec_id: String(d.exec_id),\n exit_code: Number(d.exit_code),\n stdout: String(d.stdout),\n stderr: String(d.stderr),\n wall_ms: Number(d.wall_ms),\n };\n}\n\nexport class BoxerClient {\n private readonly baseUrl: string;\n private readonly timeout: number;\n\n constructor(options: BoxerClientOptions = {}) {\n this.baseUrl = (options.baseUrl ?? \"http://localhost:8080\").replace(/\\/$/, \"\");\n this.timeout = options.timeout ?? 120_000;\n }\n\n private async fetch(path: string, init: RequestInit = {}): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n return await fetch(`${this.baseUrl}${path}`, {\n ...init,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n }\n\n async health(): Promise<boolean> {\n const res = await this.fetch(\"/healthz\");\n return res.ok;\n }\n\n async run(image: string, cmd: string[], options: RunOptions = {}): Promise<RunResult> {\n const body = buildRunBody(image, cmd, options);\n const res = await this.fetch(\"/run\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n await raiseForStatus(res);\n return parseRunResult(await res.json());\n }\n\n async uploadFile(remotePath: string, content: Blob | Uint8Array | ArrayBuffer): Promise<void> {\n const form = new FormData();\n form.append(\"path\", remotePath);\n let blob: Blob;\n if (content instanceof Blob) {\n blob = content;\n } else if (content instanceof Uint8Array) {\n // Copy via ArrayLike<number> to ensure ArrayBuffer (not SharedArrayBuffer)\n blob = new Blob([new Uint8Array(content)], { type: \"application/octet-stream\" });\n } else {\n blob = new Blob([content], { type: \"application/octet-stream\" });\n }\n form.append(\"file\", blob, remotePath.split(\"/\").pop() ?? \"file\");\n\n const res = await this.fetch(\"/files\", { method: \"POST\", body: form });\n await raiseForStatus(res);\n }\n\n async downloadFile(path: string): Promise<Uint8Array> {\n const res = await this.fetch(`/files?${new URLSearchParams({ path }).toString()}`);\n await raiseForStatus(res);\n return new Uint8Array(await res.arrayBuffer());\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/node.ts
|
|
21
|
+
var node_exports = {};
|
|
22
|
+
__export(node_exports, {
|
|
23
|
+
uploadPath: () => uploadPath
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(node_exports);
|
|
26
|
+
var import_promises = require("fs/promises");
|
|
27
|
+
var import_node_path = require("path");
|
|
28
|
+
async function uploadPath(client, localPath, remotePath) {
|
|
29
|
+
const info = await (0, import_promises.stat)(localPath);
|
|
30
|
+
if (info.isDirectory()) {
|
|
31
|
+
const prefix = remotePath ?? (0, import_node_path.basename)(localPath);
|
|
32
|
+
const uploaded = [];
|
|
33
|
+
await uploadDir(client, localPath, localPath, prefix, uploaded);
|
|
34
|
+
return uploaded.sort();
|
|
35
|
+
}
|
|
36
|
+
const dest = remotePath ?? (0, import_node_path.basename)(localPath);
|
|
37
|
+
const content = await (0, import_promises.readFile)(localPath);
|
|
38
|
+
await client.uploadFile(dest, content);
|
|
39
|
+
return [dest];
|
|
40
|
+
}
|
|
41
|
+
async function uploadDir(client, rootDir, currentDir, prefix, uploaded) {
|
|
42
|
+
const entries = await (0, import_promises.readdir)(currentDir, { withFileTypes: true });
|
|
43
|
+
await Promise.all(
|
|
44
|
+
entries.map(async (entry) => {
|
|
45
|
+
const fullPath = (0, import_node_path.join)(currentDir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
await uploadDir(client, rootDir, fullPath, prefix, uploaded);
|
|
48
|
+
} else if (entry.isFile()) {
|
|
49
|
+
const rel = (0, import_node_path.relative)(rootDir, fullPath);
|
|
50
|
+
const dest = `${prefix}/${rel.split("\\").join("/")}`;
|
|
51
|
+
const content = await (0, import_promises.readFile)(fullPath);
|
|
52
|
+
await client.uploadFile(dest, content);
|
|
53
|
+
uploaded.push(dest);
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
59
|
+
0 && (module.exports = {
|
|
60
|
+
uploadPath
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=node.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node.ts"],"sourcesContent":["import { readFile, readdir, stat } from \"node:fs/promises\";\nimport { basename, join, relative } from \"node:path\";\nimport type { BoxerClient } from \"./client.js\";\n\n/**\n * Upload a local file or directory to the Boxer file store.\n *\n * If `localPath` is a directory, all files inside it are uploaded recursively,\n * preserving the directory structure under `remotePath` (defaults to the\n * directory name).\n *\n * Returns the list of remote paths that were uploaded.\n *\n * Note: Requires a runtime with Node.js-compatible `fs` APIs (Node, Bun, Deno).\n */\nexport async function uploadPath(\n client: BoxerClient,\n localPath: string,\n remotePath?: string,\n): Promise<string[]> {\n const info = await stat(localPath);\n\n if (info.isDirectory()) {\n const prefix = remotePath ?? basename(localPath);\n const uploaded: string[] = [];\n await uploadDir(client, localPath, localPath, prefix, uploaded);\n return uploaded.sort();\n }\n\n const dest = remotePath ?? basename(localPath);\n const content = await readFile(localPath);\n await client.uploadFile(dest, content);\n return [dest];\n}\n\nasync function uploadDir(\n client: BoxerClient,\n rootDir: string,\n currentDir: string,\n prefix: string,\n uploaded: string[],\n): Promise<void> {\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n await Promise.all(\n entries.map(async (entry) => {\n const fullPath = join(currentDir, entry.name);\n if (entry.isDirectory()) {\n await uploadDir(client, rootDir, fullPath, prefix, uploaded);\n } else if (entry.isFile()) {\n const rel = relative(rootDir, fullPath);\n const dest = `${prefix}/${rel.split(\"\\\\\").join(\"/\")}`;\n const content = await readFile(fullPath);\n await client.uploadFile(dest, content);\n uploaded.push(dest);\n }\n }),\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAAwC;AACxC,uBAAyC;AAczC,eAAsB,WACpB,QACA,WACA,YACmB;AACnB,QAAM,OAAO,UAAM,sBAAK,SAAS;AAEjC,MAAI,KAAK,YAAY,GAAG;AACtB,UAAM,SAAS,kBAAc,2BAAS,SAAS;AAC/C,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAU,QAAQ,WAAW,WAAW,QAAQ,QAAQ;AAC9D,WAAO,SAAS,KAAK;AAAA,EACvB;AAEA,QAAM,OAAO,kBAAc,2BAAS,SAAS;AAC7C,QAAM,UAAU,UAAM,0BAAS,SAAS;AACxC,QAAM,OAAO,WAAW,MAAM,OAAO;AACrC,SAAO,CAAC,IAAI;AACd;AAEA,eAAe,UACb,QACA,SACA,YACA,QACA,UACe;AACf,QAAM,UAAU,UAAM,yBAAQ,YAAY,EAAE,eAAe,KAAK,CAAC;AAEjE,QAAM,QAAQ;AAAA,IACZ,QAAQ,IAAI,OAAO,UAAU;AAC3B,YAAM,eAAW,uBAAK,YAAY,MAAM,IAAI;AAC5C,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,UAAU,QAAQ,SAAS,UAAU,QAAQ,QAAQ;AAAA,MAC7D,WAAW,MAAM,OAAO,GAAG;AACzB,cAAM,UAAM,2BAAS,SAAS,QAAQ;AACtC,cAAM,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC;AACnD,cAAM,UAAU,UAAM,0BAAS,QAAQ;AACvC,cAAM,OAAO,WAAW,MAAM,OAAO;AACrC,iBAAS,KAAK,IAAI;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
package/dist/node.d.cts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { B as BoxerClient } from './client-DpDp9i-R.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Upload a local file or directory to the Boxer file store.
|
|
5
|
+
*
|
|
6
|
+
* If `localPath` is a directory, all files inside it are uploaded recursively,
|
|
7
|
+
* preserving the directory structure under `remotePath` (defaults to the
|
|
8
|
+
* directory name).
|
|
9
|
+
*
|
|
10
|
+
* Returns the list of remote paths that were uploaded.
|
|
11
|
+
*
|
|
12
|
+
* Note: Requires a runtime with Node.js-compatible `fs` APIs (Node, Bun, Deno).
|
|
13
|
+
*/
|
|
14
|
+
declare function uploadPath(client: BoxerClient, localPath: string, remotePath?: string): Promise<string[]>;
|
|
15
|
+
|
|
16
|
+
export { uploadPath };
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { B as BoxerClient } from './client-DpDp9i-R.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Upload a local file or directory to the Boxer file store.
|
|
5
|
+
*
|
|
6
|
+
* If `localPath` is a directory, all files inside it are uploaded recursively,
|
|
7
|
+
* preserving the directory structure under `remotePath` (defaults to the
|
|
8
|
+
* directory name).
|
|
9
|
+
*
|
|
10
|
+
* Returns the list of remote paths that were uploaded.
|
|
11
|
+
*
|
|
12
|
+
* Note: Requires a runtime with Node.js-compatible `fs` APIs (Node, Bun, Deno).
|
|
13
|
+
*/
|
|
14
|
+
declare function uploadPath(client: BoxerClient, localPath: string, remotePath?: string): Promise<string[]>;
|
|
15
|
+
|
|
16
|
+
export { uploadPath };
|
package/dist/node.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/node.ts
|
|
2
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
3
|
+
import { basename, join, relative } from "path";
|
|
4
|
+
async function uploadPath(client, localPath, remotePath) {
|
|
5
|
+
const info = await stat(localPath);
|
|
6
|
+
if (info.isDirectory()) {
|
|
7
|
+
const prefix = remotePath ?? basename(localPath);
|
|
8
|
+
const uploaded = [];
|
|
9
|
+
await uploadDir(client, localPath, localPath, prefix, uploaded);
|
|
10
|
+
return uploaded.sort();
|
|
11
|
+
}
|
|
12
|
+
const dest = remotePath ?? basename(localPath);
|
|
13
|
+
const content = await readFile(localPath);
|
|
14
|
+
await client.uploadFile(dest, content);
|
|
15
|
+
return [dest];
|
|
16
|
+
}
|
|
17
|
+
async function uploadDir(client, rootDir, currentDir, prefix, uploaded) {
|
|
18
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
19
|
+
await Promise.all(
|
|
20
|
+
entries.map(async (entry) => {
|
|
21
|
+
const fullPath = join(currentDir, entry.name);
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
await uploadDir(client, rootDir, fullPath, prefix, uploaded);
|
|
24
|
+
} else if (entry.isFile()) {
|
|
25
|
+
const rel = relative(rootDir, fullPath);
|
|
26
|
+
const dest = `${prefix}/${rel.split("\\").join("/")}`;
|
|
27
|
+
const content = await readFile(fullPath);
|
|
28
|
+
await client.uploadFile(dest, content);
|
|
29
|
+
uploaded.push(dest);
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
uploadPath
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=node.js.map
|
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node.ts"],"sourcesContent":["import { readFile, readdir, stat } from \"node:fs/promises\";\nimport { basename, join, relative } from \"node:path\";\nimport type { BoxerClient } from \"./client.js\";\n\n/**\n * Upload a local file or directory to the Boxer file store.\n *\n * If `localPath` is a directory, all files inside it are uploaded recursively,\n * preserving the directory structure under `remotePath` (defaults to the\n * directory name).\n *\n * Returns the list of remote paths that were uploaded.\n *\n * Note: Requires a runtime with Node.js-compatible `fs` APIs (Node, Bun, Deno).\n */\nexport async function uploadPath(\n client: BoxerClient,\n localPath: string,\n remotePath?: string,\n): Promise<string[]> {\n const info = await stat(localPath);\n\n if (info.isDirectory()) {\n const prefix = remotePath ?? basename(localPath);\n const uploaded: string[] = [];\n await uploadDir(client, localPath, localPath, prefix, uploaded);\n return uploaded.sort();\n }\n\n const dest = remotePath ?? basename(localPath);\n const content = await readFile(localPath);\n await client.uploadFile(dest, content);\n return [dest];\n}\n\nasync function uploadDir(\n client: BoxerClient,\n rootDir: string,\n currentDir: string,\n prefix: string,\n uploaded: string[],\n): Promise<void> {\n const entries = await readdir(currentDir, { withFileTypes: true });\n\n await Promise.all(\n entries.map(async (entry) => {\n const fullPath = join(currentDir, entry.name);\n if (entry.isDirectory()) {\n await uploadDir(client, rootDir, fullPath, prefix, uploaded);\n } else if (entry.isFile()) {\n const rel = relative(rootDir, fullPath);\n const dest = `${prefix}/${rel.split(\"\\\\\").join(\"/\")}`;\n const content = await readFile(fullPath);\n await client.uploadFile(dest, content);\n uploaded.push(dest);\n }\n }),\n );\n}\n"],"mappings":";AAAA,SAAS,UAAU,SAAS,YAAY;AACxC,SAAS,UAAU,MAAM,gBAAgB;AAczC,eAAsB,WACpB,QACA,WACA,YACmB;AACnB,QAAM,OAAO,MAAM,KAAK,SAAS;AAEjC,MAAI,KAAK,YAAY,GAAG;AACtB,UAAM,SAAS,cAAc,SAAS,SAAS;AAC/C,UAAM,WAAqB,CAAC;AAC5B,UAAM,UAAU,QAAQ,WAAW,WAAW,QAAQ,QAAQ;AAC9D,WAAO,SAAS,KAAK;AAAA,EACvB;AAEA,QAAM,OAAO,cAAc,SAAS,SAAS;AAC7C,QAAM,UAAU,MAAM,SAAS,SAAS;AACxC,QAAM,OAAO,WAAW,MAAM,OAAO;AACrC,SAAO,CAAC,IAAI;AACd;AAEA,eAAe,UACb,QACA,SACA,YACA,QACA,UACe;AACf,QAAM,UAAU,MAAM,QAAQ,YAAY,EAAE,eAAe,KAAK,CAAC;AAEjE,QAAM,QAAQ;AAAA,IACZ,QAAQ,IAAI,OAAO,UAAU;AAC3B,YAAM,WAAW,KAAK,YAAY,MAAM,IAAI;AAC5C,UAAI,MAAM,YAAY,GAAG;AACvB,cAAM,UAAU,QAAQ,SAAS,UAAU,QAAQ,QAAQ;AAAA,MAC7D,WAAW,MAAM,OAAO,GAAG;AACzB,cAAM,MAAM,SAAS,SAAS,QAAQ;AACtC,cAAM,OAAO,GAAG,MAAM,IAAI,IAAI,MAAM,IAAI,EAAE,KAAK,GAAG,CAAC;AACnD,cAAM,UAAU,MAAM,SAAS,QAAQ;AACvC,cAAM,OAAO,WAAW,MAAM,OAAO;AACrC,iBAAS,KAAK,IAAI;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "boxer-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for the Boxer sandboxed container execution service",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"author": "Alexander Kolupaev <theonekeyg@gmail.com>",
|
|
7
|
+
"homepage": "https://github.com/theonekeyg/boxer",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./node": {
|
|
16
|
+
"types": "./dist/node.d.ts",
|
|
17
|
+
"import": "./dist/node.mjs",
|
|
18
|
+
"require": "./dist/node.cjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.cjs",
|
|
22
|
+
"module": "./dist/index.mjs",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"files": ["dist"],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"lint": "biome check .",
|
|
28
|
+
"lint:fix": "biome check --write .",
|
|
29
|
+
"format": "biome format --write .",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
},
|
|
33
|
+
"pnpm": {
|
|
34
|
+
"onlyBuiltDependencies": ["@biomejs/biome", "esbuild"]
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@biomejs/biome": "^1.9.4",
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.6.0",
|
|
41
|
+
"vitest": "^2.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|