@tinysandbox/tinysandbox 0.3.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/index.d.ts ADDED
@@ -0,0 +1,132 @@
1
+ import type {
2
+ CommandTiming,
3
+ DirEntryJs,
4
+ ExecResult,
5
+ FileStat,
6
+ NativeSandbox,
7
+ OpenModeJs,
8
+ SandboxFs,
9
+ SandboxStats,
10
+ VfsStatsJs
11
+ } from './native'
12
+
13
+ export {
14
+ CommandTiming,
15
+ DirEntryJs,
16
+ ExecResult,
17
+ FileStat,
18
+ NativeSandbox,
19
+ OpenModeJs,
20
+ SandboxFs,
21
+ SandboxStats,
22
+ VfsStatsJs
23
+ }
24
+
25
+ export declare class Sandbox extends NativeSandbox {
26
+ constructor(options?: SandboxOptions | null)
27
+ override get fs(): SandboxFs
28
+ override stats(): Promise<SandboxStats>
29
+ }
30
+
31
+ export interface SandboxOptions {
32
+ limits?: LimitsOptions
33
+ env?: Record<string, string>
34
+ cwd?: string
35
+ persistSession?: boolean
36
+ commands?: Record<string, JsCommand>
37
+ vfs?: JsVfs
38
+ }
39
+
40
+ export interface LimitsOptions {
41
+ /** Milliseconds; must be finite and non-negative. */
42
+ wallTimeMs?: number
43
+ /** Byte counts must be non-negative integers at or below Number.MAX_SAFE_INTEGER. */
44
+ stdoutBytes?: number
45
+ stderrBytes?: number
46
+ maxCommands?: number
47
+ sortInputBytes?: number
48
+ wasmMemoryBytes?: number
49
+ }
50
+
51
+ export type JsCommand = (call: CommandCall) => Promise<CommandOutput> | CommandOutput
52
+
53
+ export interface CommandCall {
54
+ args: Array<string>
55
+ env: Record<string, string>
56
+ cwd: string
57
+ stdin: Buffer
58
+ }
59
+
60
+ export interface CommandOutput {
61
+ exitCode?: number
62
+ stdout?: Buffer | Uint8Array | string
63
+ stderr?: Buffer | Uint8Array | string
64
+ }
65
+
66
+ export interface JsVfs {
67
+ stat(request: VfsRequest): Promise<VfsResponse> | VfsResponse
68
+ readdir(request: VfsRequest): Promise<Array<DirEntryJs> | VfsResponse> | Array<DirEntryJs> | VfsResponse
69
+ mkdir(request: VfsRequest): Promise<VfsResponse | void> | VfsResponse | void
70
+ rename(request: VfsRequest): Promise<VfsResponse | void> | VfsResponse | void
71
+ unlink(request: VfsRequest): Promise<VfsResponse | void> | VfsResponse | void
72
+ rmdir(request: VfsRequest): Promise<VfsResponse | void> | VfsResponse | void
73
+ open(request: VfsRequest): Promise<number | VfsResponse> | number | VfsResponse
74
+ readAt(request: VfsRequest): Promise<Buffer | VfsResponse> | Buffer | VfsResponse
75
+ writeAt(request: VfsRequest): Promise<number | VfsResponse> | number | VfsResponse
76
+ truncate(request: VfsRequest): Promise<VfsResponse | void> | VfsResponse | void
77
+ close(request: VfsRequest): Promise<VfsResponse | void> | VfsResponse | void
78
+ stats?(request: VfsRequest): Promise<VfsResponse> | VfsResponse
79
+ }
80
+
81
+ export interface VfsRequest {
82
+ path?: string
83
+ from?: string
84
+ to?: string
85
+ mode?: OpenModeJs
86
+ handle?: number
87
+ /** Handle, offset, and len values must be safe JS integers. */
88
+ offset?: number
89
+ len?: number
90
+ data?: Buffer
91
+ }
92
+
93
+ export interface VfsResponse {
94
+ fileType?: 'file' | 'directory'
95
+ len?: number
96
+ entries?: Array<DirEntryJs>
97
+ handle?: number
98
+ bytesRead?: number
99
+ bytesWritten?: number
100
+ data?: Buffer
101
+ usedBytes?: number
102
+ fileCount?: number
103
+ error?: VfsCallbackError
104
+ }
105
+
106
+ export interface VfsCallbackError {
107
+ /** Unknown codes are treated as EINVAL by the native adapter. */
108
+ code?: VfsErrno
109
+ message?: string
110
+ }
111
+
112
+ export type VfsErrno =
113
+ | 'EBADF'
114
+ | 'EBUSY'
115
+ | 'EACCES'
116
+ | 'EEXIST'
117
+ | 'EINVAL'
118
+ | 'EISDIR'
119
+ | 'ENOENT'
120
+ | 'ENOSPC'
121
+ | 'ENOTDIR'
122
+ | 'ENOTEMPTY'
123
+
124
+ export declare function runConformance(
125
+ vfsFactory: (quota: VfsQuota) => JsVfs | Promise<JsVfs>
126
+ ): Promise<{ ok: true; snapshots: 'unsupported' }>
127
+
128
+ export interface VfsQuota {
129
+ maxBytes: number
130
+ maxFiles: number
131
+ maxFileSize: number
132
+ }
package/index.js ADDED
@@ -0,0 +1,225 @@
1
+ const native = require('./native.cjs')
2
+
3
+ class Sandbox extends native.NativeSandbox {
4
+ constructor(options = undefined) {
5
+ super(normalizeOptions(options))
6
+ this._fs = undefined
7
+ }
8
+
9
+ get fs() {
10
+ this._fs ??= new SandboxFs(super.fs)
11
+ return this._fs
12
+ }
13
+ }
14
+
15
+ async function runConformance(vfsFactory) {
16
+ return native.runConformance(async (quota) => native.createJsVfs(wrapVfs(await vfsFactory(firstArgument(quota)))))
17
+ }
18
+
19
+ function normalizeOptions(options) {
20
+ if (!options) return options
21
+ const normalized = { ...options }
22
+ if (options.commands) normalized.commands = wrapCommands(options.commands)
23
+ else delete normalized.commands
24
+ if (options.vfs) normalized.vfs = wrapVfs(options.vfs)
25
+ else delete normalized.vfs
26
+ return normalized
27
+ }
28
+
29
+ function wrapCommands(commands) {
30
+ return Object.fromEntries(
31
+ Object.entries(commands).map(([name, command]) => [
32
+ name,
33
+ async (call) => {
34
+ try {
35
+ const payload = firstArgument(call)
36
+ return normalizeCommandOutput(await command(payload))
37
+ } catch (err) {
38
+ return {
39
+ exitCode: 1,
40
+ stderr: Buffer.from(`${err?.message ?? err}\n`)
41
+ }
42
+ }
43
+ }
44
+ ])
45
+ )
46
+ }
47
+
48
+ function wrapVfs(vfs) {
49
+ return Object.fromEntries(
50
+ vfsOperations.map((name) => [
51
+ name,
52
+ async (request) => {
53
+ try {
54
+ return normalizeVfsResponse(name, await vfs[name](normalizeVfsRequest(request)))
55
+ } catch (err) {
56
+ return { error: errorPayload(err) }
57
+ }
58
+ }
59
+ ]).concat(
60
+ typeof vfs.stats === 'function'
61
+ ? [[
62
+ 'stats',
63
+ async (request) => {
64
+ try {
65
+ return normalizeVfsResponse('stats', await vfs.stats(normalizeVfsRequest(request)))
66
+ } catch (err) {
67
+ return { error: errorPayload(err) }
68
+ }
69
+ }
70
+ ]]
71
+ : []
72
+ )
73
+ )
74
+ }
75
+
76
+ function normalizeVfsRequest(request) {
77
+ return firstArgument(request)
78
+ }
79
+
80
+ function firstArgument(value) {
81
+ // napi-rs TSFN callbacks marshal tuple arguments as an object with numeric
82
+ // keys, while direct wrapper calls already pass the request object.
83
+ return value && typeof value === 'object' && Object.hasOwn(value, '0') ? value[0] : value
84
+ }
85
+
86
+ function normalizeCommandOutput(output = {}) {
87
+ return {
88
+ exitCode: output.exitCode ?? 0,
89
+ stdout: output.stdout ? Buffer.from(output.stdout) : undefined,
90
+ stderr: output.stderr ? Buffer.from(output.stderr) : undefined
91
+ }
92
+ }
93
+
94
+ function normalizeVfsResponse(name, response) {
95
+ if (response === undefined || response === null) return {}
96
+ if (response.error) return response
97
+ if (Buffer.isBuffer(response)) return { data: response, bytesRead: response.length }
98
+ if (Array.isArray(response)) return { entries: response }
99
+ if (typeof response === 'number') {
100
+ if (name === 'open') return { handle: response }
101
+ if (name === 'writeAt') return { bytesWritten: response }
102
+ }
103
+ return response
104
+ }
105
+
106
+ function errorPayload(err) {
107
+ return {
108
+ code: normalizeErrnoCode(err?.code),
109
+ message: err?.message ?? String(err)
110
+ }
111
+ }
112
+
113
+ function normalizeErrnoCode(code) {
114
+ return errnos.includes(code) ? code : 'EINVAL'
115
+ }
116
+
117
+ const vfsOperations = [
118
+ 'stat',
119
+ 'readdir',
120
+ 'mkdir',
121
+ 'rename',
122
+ 'unlink',
123
+ 'rmdir',
124
+ 'open',
125
+ 'readAt',
126
+ 'writeAt',
127
+ 'truncate',
128
+ 'close'
129
+ ]
130
+
131
+ const errnos = [
132
+ 'EBADF',
133
+ 'EBUSY',
134
+ 'EACCES',
135
+ 'EEXIST',
136
+ 'EINVAL',
137
+ 'EISDIR',
138
+ 'ENOENT',
139
+ 'ENOSPC',
140
+ 'ENOTDIR',
141
+ 'ENOTEMPTY'
142
+ ]
143
+
144
+ class SandboxFs {
145
+ constructor(inner) {
146
+ this.inner = inner
147
+ }
148
+
149
+ stat(path) {
150
+ return decorateFsPromise(this.inner.stat(path))
151
+ }
152
+
153
+ readdir(path) {
154
+ return decorateFsPromise(this.inner.readdir(path))
155
+ }
156
+
157
+ mkdir(path) {
158
+ return decorateFsPromise(this.inner.mkdir(path))
159
+ }
160
+
161
+ rename(from, to) {
162
+ return decorateFsPromise(this.inner.rename(from, to))
163
+ }
164
+
165
+ unlink(path) {
166
+ return decorateFsPromise(this.inner.unlink(path))
167
+ }
168
+
169
+ rmdir(path) {
170
+ return decorateFsPromise(this.inner.rmdir(path))
171
+ }
172
+
173
+ readFile(path) {
174
+ return decorateFsPromise(this.inner.readFile(path))
175
+ }
176
+
177
+ writeFile(path, data) {
178
+ return decorateFsPromise(this.inner.writeFile(path, data))
179
+ }
180
+
181
+ appendFile(path, data) {
182
+ return decorateFsPromise(this.inner.appendFile(path, data))
183
+ }
184
+
185
+ open(path, mode) {
186
+ return decorateFsPromise(this.inner.open(path, mode))
187
+ }
188
+
189
+ readAt(handle, offset, len) {
190
+ return decorateFsPromise(this.inner.readAt(handle, offset, len))
191
+ }
192
+
193
+ writeAt(handle, offset, data) {
194
+ return decorateFsPromise(this.inner.writeAt(handle, offset, data))
195
+ }
196
+
197
+ truncate(handle, len) {
198
+ return decorateFsPromise(this.inner.truncate(handle, len))
199
+ }
200
+
201
+ close(handle) {
202
+ return decorateFsPromise(this.inner.close(handle))
203
+ }
204
+ }
205
+
206
+ async function decorateFsPromise(promise) {
207
+ try {
208
+ return await promise
209
+ } catch (err) {
210
+ if (!err || typeof err !== 'object') throw err
211
+ err.code = normalizeNativeErrno(err)
212
+ throw err
213
+ }
214
+ }
215
+
216
+ function normalizeNativeErrno(err) {
217
+ if (errnos.includes(err.code)) return err.code
218
+ const [prefix] = String(err.message ?? '').split(':', 1)
219
+ return errnos.includes(prefix) ? prefix : 'EINVAL'
220
+ }
221
+
222
+ exports.NativeSandbox = native.NativeSandbox
223
+ exports.SandboxFs = SandboxFs
224
+ exports.Sandbox = Sandbox
225
+ exports.runConformance = runConformance
package/native.cjs ADDED
@@ -0,0 +1,594 @@
1
+ // prettier-ignore
2
+ /* eslint-disable */
3
+ // @ts-nocheck
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ const { readFileSync } = require('fs')
7
+ let nativeBinding = null
8
+ const loadErrors = []
9
+
10
+ const isMusl = () => {
11
+ let musl = false
12
+ if (process.platform === 'linux') {
13
+ musl = isMuslFromFilesystem()
14
+ if (musl === null) {
15
+ musl = isMuslFromReport()
16
+ }
17
+ if (musl === null) {
18
+ musl = isMuslFromChildProcess()
19
+ }
20
+ }
21
+ return musl
22
+ }
23
+
24
+ const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')
25
+
26
+ const isMuslFromFilesystem = () => {
27
+ try {
28
+ return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')
29
+ } catch {
30
+ return null
31
+ }
32
+ }
33
+
34
+ const isMuslFromReport = () => {
35
+ let report = null
36
+ if (process.report && typeof process.report.getReport === 'function') {
37
+ process.report.excludeNetwork = true
38
+ report = process.report.getReport()
39
+ }
40
+ if (!report) {
41
+ return null
42
+ }
43
+ if (report.header && report.header.glibcVersionRuntime) {
44
+ return false
45
+ }
46
+ if (Array.isArray(report.sharedObjects)) {
47
+ if (report.sharedObjects.some(isFileMusl)) {
48
+ return true
49
+ }
50
+ }
51
+ return false
52
+ }
53
+
54
+ const isMuslFromChildProcess = () => {
55
+ try {
56
+ return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
57
+ } catch (e) {
58
+ // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false
59
+ return false
60
+ }
61
+ }
62
+
63
+ function requireNative() {
64
+ if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
65
+ try {
66
+ return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);
67
+ } catch (err) {
68
+ loadErrors.push(err)
69
+ }
70
+ } else if (process.platform === 'android') {
71
+ if (process.arch === 'arm64') {
72
+ try {
73
+ return require('./tinysandbox-node.android-arm64.node')
74
+ } catch (e) {
75
+ loadErrors.push(e)
76
+ }
77
+ try {
78
+ const binding = require('tinysandbox-android-arm64')
79
+ const bindingPackageVersion = require('tinysandbox-android-arm64/package.json').version
80
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
81
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
82
+ }
83
+ return binding
84
+ } catch (e) {
85
+ loadErrors.push(e)
86
+ }
87
+ } else if (process.arch === 'arm') {
88
+ try {
89
+ return require('./tinysandbox-node.android-arm-eabi.node')
90
+ } catch (e) {
91
+ loadErrors.push(e)
92
+ }
93
+ try {
94
+ const binding = require('tinysandbox-android-arm-eabi')
95
+ const bindingPackageVersion = require('tinysandbox-android-arm-eabi/package.json').version
96
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
97
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
98
+ }
99
+ return binding
100
+ } catch (e) {
101
+ loadErrors.push(e)
102
+ }
103
+ } else {
104
+ loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))
105
+ }
106
+ } else if (process.platform === 'win32') {
107
+ if (process.arch === 'x64') {
108
+ if ((process.config && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || (process.config && process.config.variables && process.config.variables.node_target_type === 'shared_library')) {
109
+ try {
110
+ return require('./tinysandbox-node.win32-x64-gnu.node')
111
+ } catch (e) {
112
+ loadErrors.push(e)
113
+ }
114
+ try {
115
+ const binding = require('tinysandbox-win32-x64-gnu')
116
+ const bindingPackageVersion = require('tinysandbox-win32-x64-gnu/package.json').version
117
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
118
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
119
+ }
120
+ return binding
121
+ } catch (e) {
122
+ loadErrors.push(e)
123
+ }
124
+ } else {
125
+ try {
126
+ return require('./tinysandbox-node.win32-x64-msvc.node')
127
+ } catch (e) {
128
+ loadErrors.push(e)
129
+ }
130
+ try {
131
+ const binding = require('tinysandbox-win32-x64-msvc')
132
+ const bindingPackageVersion = require('tinysandbox-win32-x64-msvc/package.json').version
133
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
134
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
135
+ }
136
+ return binding
137
+ } catch (e) {
138
+ loadErrors.push(e)
139
+ }
140
+ }
141
+ } else if (process.arch === 'ia32') {
142
+ try {
143
+ return require('./tinysandbox-node.win32-ia32-msvc.node')
144
+ } catch (e) {
145
+ loadErrors.push(e)
146
+ }
147
+ try {
148
+ const binding = require('tinysandbox-win32-ia32-msvc')
149
+ const bindingPackageVersion = require('tinysandbox-win32-ia32-msvc/package.json').version
150
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
151
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
152
+ }
153
+ return binding
154
+ } catch (e) {
155
+ loadErrors.push(e)
156
+ }
157
+ } else if (process.arch === 'arm64') {
158
+ try {
159
+ return require('./tinysandbox-node.win32-arm64-msvc.node')
160
+ } catch (e) {
161
+ loadErrors.push(e)
162
+ }
163
+ try {
164
+ const binding = require('tinysandbox-win32-arm64-msvc')
165
+ const bindingPackageVersion = require('tinysandbox-win32-arm64-msvc/package.json').version
166
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
167
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
168
+ }
169
+ return binding
170
+ } catch (e) {
171
+ loadErrors.push(e)
172
+ }
173
+ } else {
174
+ loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`))
175
+ }
176
+ } else if (process.platform === 'darwin') {
177
+ try {
178
+ return require('./tinysandbox-node.darwin-universal.node')
179
+ } catch (e) {
180
+ loadErrors.push(e)
181
+ }
182
+ try {
183
+ const binding = require('tinysandbox-darwin-universal')
184
+ const bindingPackageVersion = require('tinysandbox-darwin-universal/package.json').version
185
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
186
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
187
+ }
188
+ return binding
189
+ } catch (e) {
190
+ loadErrors.push(e)
191
+ }
192
+ if (process.arch === 'x64') {
193
+ try {
194
+ return require('./tinysandbox-node.darwin-x64.node')
195
+ } catch (e) {
196
+ loadErrors.push(e)
197
+ }
198
+ try {
199
+ const binding = require('tinysandbox-darwin-x64')
200
+ const bindingPackageVersion = require('tinysandbox-darwin-x64/package.json').version
201
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
202
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
203
+ }
204
+ return binding
205
+ } catch (e) {
206
+ loadErrors.push(e)
207
+ }
208
+ } else if (process.arch === 'arm64') {
209
+ try {
210
+ return require('./tinysandbox-node.darwin-arm64.node')
211
+ } catch (e) {
212
+ loadErrors.push(e)
213
+ }
214
+ try {
215
+ const binding = require('tinysandbox-darwin-arm64')
216
+ const bindingPackageVersion = require('tinysandbox-darwin-arm64/package.json').version
217
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
218
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
219
+ }
220
+ return binding
221
+ } catch (e) {
222
+ loadErrors.push(e)
223
+ }
224
+ } else {
225
+ loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))
226
+ }
227
+ } else if (process.platform === 'freebsd') {
228
+ if (process.arch === 'x64') {
229
+ try {
230
+ return require('./tinysandbox-node.freebsd-x64.node')
231
+ } catch (e) {
232
+ loadErrors.push(e)
233
+ }
234
+ try {
235
+ const binding = require('tinysandbox-freebsd-x64')
236
+ const bindingPackageVersion = require('tinysandbox-freebsd-x64/package.json').version
237
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
238
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
239
+ }
240
+ return binding
241
+ } catch (e) {
242
+ loadErrors.push(e)
243
+ }
244
+ } else if (process.arch === 'arm64') {
245
+ try {
246
+ return require('./tinysandbox-node.freebsd-arm64.node')
247
+ } catch (e) {
248
+ loadErrors.push(e)
249
+ }
250
+ try {
251
+ const binding = require('tinysandbox-freebsd-arm64')
252
+ const bindingPackageVersion = require('tinysandbox-freebsd-arm64/package.json').version
253
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
254
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
255
+ }
256
+ return binding
257
+ } catch (e) {
258
+ loadErrors.push(e)
259
+ }
260
+ } else {
261
+ loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`))
262
+ }
263
+ } else if (process.platform === 'linux') {
264
+ if (process.arch === 'x64') {
265
+ if (isMusl()) {
266
+ try {
267
+ return require('./tinysandbox-node.linux-x64-musl.node')
268
+ } catch (e) {
269
+ loadErrors.push(e)
270
+ }
271
+ try {
272
+ const binding = require('tinysandbox-linux-x64-musl')
273
+ const bindingPackageVersion = require('tinysandbox-linux-x64-musl/package.json').version
274
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
275
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
276
+ }
277
+ return binding
278
+ } catch (e) {
279
+ loadErrors.push(e)
280
+ }
281
+ } else {
282
+ try {
283
+ return require('./tinysandbox-node.linux-x64-gnu.node')
284
+ } catch (e) {
285
+ loadErrors.push(e)
286
+ }
287
+ try {
288
+ const binding = require('tinysandbox-linux-x64-gnu')
289
+ const bindingPackageVersion = require('tinysandbox-linux-x64-gnu/package.json').version
290
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
291
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
292
+ }
293
+ return binding
294
+ } catch (e) {
295
+ loadErrors.push(e)
296
+ }
297
+ }
298
+ } else if (process.arch === 'arm64') {
299
+ if (isMusl()) {
300
+ try {
301
+ return require('./tinysandbox-node.linux-arm64-musl.node')
302
+ } catch (e) {
303
+ loadErrors.push(e)
304
+ }
305
+ try {
306
+ const binding = require('tinysandbox-linux-arm64-musl')
307
+ const bindingPackageVersion = require('tinysandbox-linux-arm64-musl/package.json').version
308
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
309
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
310
+ }
311
+ return binding
312
+ } catch (e) {
313
+ loadErrors.push(e)
314
+ }
315
+ } else {
316
+ try {
317
+ return require('./tinysandbox-node.linux-arm64-gnu.node')
318
+ } catch (e) {
319
+ loadErrors.push(e)
320
+ }
321
+ try {
322
+ const binding = require('tinysandbox-linux-arm64-gnu')
323
+ const bindingPackageVersion = require('tinysandbox-linux-arm64-gnu/package.json').version
324
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
325
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
326
+ }
327
+ return binding
328
+ } catch (e) {
329
+ loadErrors.push(e)
330
+ }
331
+ }
332
+ } else if (process.arch === 'arm') {
333
+ if (isMusl()) {
334
+ try {
335
+ return require('./tinysandbox-node.linux-arm-musleabihf.node')
336
+ } catch (e) {
337
+ loadErrors.push(e)
338
+ }
339
+ try {
340
+ const binding = require('tinysandbox-linux-arm-musleabihf')
341
+ const bindingPackageVersion = require('tinysandbox-linux-arm-musleabihf/package.json').version
342
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
343
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
344
+ }
345
+ return binding
346
+ } catch (e) {
347
+ loadErrors.push(e)
348
+ }
349
+ } else {
350
+ try {
351
+ return require('./tinysandbox-node.linux-arm-gnueabihf.node')
352
+ } catch (e) {
353
+ loadErrors.push(e)
354
+ }
355
+ try {
356
+ const binding = require('tinysandbox-linux-arm-gnueabihf')
357
+ const bindingPackageVersion = require('tinysandbox-linux-arm-gnueabihf/package.json').version
358
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
359
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
360
+ }
361
+ return binding
362
+ } catch (e) {
363
+ loadErrors.push(e)
364
+ }
365
+ }
366
+ } else if (process.arch === 'loong64') {
367
+ if (isMusl()) {
368
+ try {
369
+ return require('./tinysandbox-node.linux-loong64-musl.node')
370
+ } catch (e) {
371
+ loadErrors.push(e)
372
+ }
373
+ try {
374
+ const binding = require('tinysandbox-linux-loong64-musl')
375
+ const bindingPackageVersion = require('tinysandbox-linux-loong64-musl/package.json').version
376
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
377
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
378
+ }
379
+ return binding
380
+ } catch (e) {
381
+ loadErrors.push(e)
382
+ }
383
+ } else {
384
+ try {
385
+ return require('./tinysandbox-node.linux-loong64-gnu.node')
386
+ } catch (e) {
387
+ loadErrors.push(e)
388
+ }
389
+ try {
390
+ const binding = require('tinysandbox-linux-loong64-gnu')
391
+ const bindingPackageVersion = require('tinysandbox-linux-loong64-gnu/package.json').version
392
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
393
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
394
+ }
395
+ return binding
396
+ } catch (e) {
397
+ loadErrors.push(e)
398
+ }
399
+ }
400
+ } else if (process.arch === 'riscv64') {
401
+ if (isMusl()) {
402
+ try {
403
+ return require('./tinysandbox-node.linux-riscv64-musl.node')
404
+ } catch (e) {
405
+ loadErrors.push(e)
406
+ }
407
+ try {
408
+ const binding = require('tinysandbox-linux-riscv64-musl')
409
+ const bindingPackageVersion = require('tinysandbox-linux-riscv64-musl/package.json').version
410
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
411
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
412
+ }
413
+ return binding
414
+ } catch (e) {
415
+ loadErrors.push(e)
416
+ }
417
+ } else {
418
+ try {
419
+ return require('./tinysandbox-node.linux-riscv64-gnu.node')
420
+ } catch (e) {
421
+ loadErrors.push(e)
422
+ }
423
+ try {
424
+ const binding = require('tinysandbox-linux-riscv64-gnu')
425
+ const bindingPackageVersion = require('tinysandbox-linux-riscv64-gnu/package.json').version
426
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
427
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
428
+ }
429
+ return binding
430
+ } catch (e) {
431
+ loadErrors.push(e)
432
+ }
433
+ }
434
+ } else if (process.arch === 'ppc64') {
435
+ try {
436
+ return require('./tinysandbox-node.linux-ppc64-gnu.node')
437
+ } catch (e) {
438
+ loadErrors.push(e)
439
+ }
440
+ try {
441
+ const binding = require('tinysandbox-linux-ppc64-gnu')
442
+ const bindingPackageVersion = require('tinysandbox-linux-ppc64-gnu/package.json').version
443
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
444
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
445
+ }
446
+ return binding
447
+ } catch (e) {
448
+ loadErrors.push(e)
449
+ }
450
+ } else if (process.arch === 's390x') {
451
+ try {
452
+ return require('./tinysandbox-node.linux-s390x-gnu.node')
453
+ } catch (e) {
454
+ loadErrors.push(e)
455
+ }
456
+ try {
457
+ const binding = require('tinysandbox-linux-s390x-gnu')
458
+ const bindingPackageVersion = require('tinysandbox-linux-s390x-gnu/package.json').version
459
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
460
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
461
+ }
462
+ return binding
463
+ } catch (e) {
464
+ loadErrors.push(e)
465
+ }
466
+ } else {
467
+ loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`))
468
+ }
469
+ } else if (process.platform === 'openharmony') {
470
+ if (process.arch === 'arm64') {
471
+ try {
472
+ return require('./tinysandbox-node.openharmony-arm64.node')
473
+ } catch (e) {
474
+ loadErrors.push(e)
475
+ }
476
+ try {
477
+ const binding = require('tinysandbox-openharmony-arm64')
478
+ const bindingPackageVersion = require('tinysandbox-openharmony-arm64/package.json').version
479
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
480
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
481
+ }
482
+ return binding
483
+ } catch (e) {
484
+ loadErrors.push(e)
485
+ }
486
+ } else if (process.arch === 'x64') {
487
+ try {
488
+ return require('./tinysandbox-node.openharmony-x64.node')
489
+ } catch (e) {
490
+ loadErrors.push(e)
491
+ }
492
+ try {
493
+ const binding = require('tinysandbox-openharmony-x64')
494
+ const bindingPackageVersion = require('tinysandbox-openharmony-x64/package.json').version
495
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
496
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
497
+ }
498
+ return binding
499
+ } catch (e) {
500
+ loadErrors.push(e)
501
+ }
502
+ } else if (process.arch === 'arm') {
503
+ try {
504
+ return require('./tinysandbox-node.openharmony-arm.node')
505
+ } catch (e) {
506
+ loadErrors.push(e)
507
+ }
508
+ try {
509
+ const binding = require('tinysandbox-openharmony-arm')
510
+ const bindingPackageVersion = require('tinysandbox-openharmony-arm/package.json').version
511
+ if (bindingPackageVersion !== '0.3.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
512
+ throw new Error(`Native binding package version mismatch, expected 0.3.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
513
+ }
514
+ return binding
515
+ } catch (e) {
516
+ loadErrors.push(e)
517
+ }
518
+ } else {
519
+ loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`))
520
+ }
521
+ } else {
522
+ loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`))
523
+ }
524
+ }
525
+
526
+ nativeBinding = requireNative()
527
+
528
+ // NAPI_RS_FORCE_WASI is a tri-state flag:
529
+ // unset / any other value → native binding preferred, WASI is only a fallback
530
+ // 'true' → force WASI fallback even if native loaded
531
+ // 'error' → force WASI and throw if no WASI binding is found
532
+ // Treating any non-empty string as truthy (the historical behavior) meant
533
+ // NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered
534
+ // the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file.
535
+ const forceWasi =
536
+ process.env.NAPI_RS_FORCE_WASI === 'true' || process.env.NAPI_RS_FORCE_WASI === 'error'
537
+
538
+ if (!nativeBinding || forceWasi) {
539
+ let wasiBinding = null
540
+ let wasiBindingError = null
541
+ try {
542
+ wasiBinding = require('./tinysandbox-node.wasi.cjs')
543
+ nativeBinding = wasiBinding
544
+ } catch (err) {
545
+ if (forceWasi) {
546
+ wasiBindingError = err
547
+ }
548
+ }
549
+ if (!nativeBinding || forceWasi) {
550
+ try {
551
+ wasiBinding = require('tinysandbox-wasm32-wasi')
552
+ nativeBinding = wasiBinding
553
+ } catch (err) {
554
+ if (forceWasi) {
555
+ if (!wasiBindingError) {
556
+ wasiBindingError = err
557
+ } else {
558
+ wasiBindingError.cause = err
559
+ }
560
+ loadErrors.push(err)
561
+ }
562
+ }
563
+ }
564
+ if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) {
565
+ const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error')
566
+ error.cause = wasiBindingError
567
+ throw error
568
+ }
569
+ }
570
+
571
+ if (!nativeBinding) {
572
+ if (loadErrors.length > 0) {
573
+ const error = new Error(
574
+ `Cannot find native binding. ` +
575
+ `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` +
576
+ 'Please try `npm i` again after removing both package-lock.json and node_modules directory.',
577
+ )
578
+ // assign instead of the `new Error(message, { cause })` options form,
579
+ // which Node < 16.9 silently ignores
580
+ error.cause = loadErrors.reduce((err, cur) => {
581
+ cur.cause = err
582
+ return cur
583
+ })
584
+ throw error
585
+ }
586
+ throw new Error(`Failed to load native binding`)
587
+ }
588
+
589
+ module.exports = nativeBinding
590
+ module.exports.NativeSandbox = nativeBinding.NativeSandbox
591
+ module.exports.Sandbox = nativeBinding.Sandbox
592
+ module.exports.SandboxFs = nativeBinding.SandboxFs
593
+ module.exports.createJsVfs = nativeBinding.createJsVfs
594
+ module.exports.runConformance = nativeBinding.runConformance
package/native.d.ts ADDED
@@ -0,0 +1,140 @@
1
+ /* auto-generated by NAPI-RS */
2
+ /* eslint-disable */
3
+
4
+ export declare class ExternalObject<T> {
5
+ readonly '': {
6
+ readonly '': unique symbol
7
+ [K: symbol]: T
8
+ }
9
+ }
10
+ export declare class NativeSandbox {
11
+ constructor(options?: object | undefined | null)
12
+ exec(script: string): Promise<ExecResult>
13
+ get fs(): SandboxFs
14
+ stats(): Promise<SandboxStats>
15
+ }
16
+ export type Sandbox = NativeSandbox
17
+
18
+ export declare class SandboxFs {
19
+ stat(path: string): Promise<FileStat>
20
+ readdir(path: string): Promise<Array<DirEntryJs>>
21
+ mkdir(path: string): Promise<void>
22
+ rename(from: string, to: string): Promise<void>
23
+ unlink(path: string): Promise<void>
24
+ rmdir(path: string): Promise<void>
25
+ readFile(path: string): Promise<Buffer>
26
+ writeFile(path: string, data: Buffer): Promise<void>
27
+ appendFile(path: string, data: Buffer): Promise<void>
28
+ open(path: string, mode: OpenModeJs): Promise<number>
29
+ readAt(handle: number, offset: number, len: number): Promise<Buffer>
30
+ writeAt(handle: number, offset: number, data: Buffer): Promise<number>
31
+ truncate(handle: number, len: number): Promise<void>
32
+ close(handle: number): Promise<void>
33
+ }
34
+
35
+ export interface CommandCall {
36
+ args: Array<string>
37
+ env: Record<string, string>
38
+ cwd: string
39
+ stdin: Buffer
40
+ }
41
+
42
+ export interface CommandOutput {
43
+ exitCode?: number
44
+ stdout?: Buffer
45
+ stderr?: Buffer
46
+ }
47
+
48
+ export interface CommandTiming {
49
+ name: string
50
+ durationMs: number
51
+ exitCode: number
52
+ }
53
+
54
+ export interface ConformanceResult {
55
+ ok: boolean
56
+ snapshots: string
57
+ }
58
+
59
+ export declare function createJsVfs(vfs: object): ExternalObject<JsVfsExternal>
60
+
61
+ export interface DirEntryJs {
62
+ name: string
63
+ fileType: string
64
+ len: number
65
+ }
66
+
67
+ export interface ExecResult {
68
+ stdout: string
69
+ stderr: string
70
+ exitCode: number
71
+ wallTimeMs: number
72
+ stdoutTruncated: boolean
73
+ stderrTruncated: boolean
74
+ pipeBytes: Array<number>
75
+ commands: Array<CommandTiming>
76
+ peakWasmMemoryBytes?: number
77
+ }
78
+
79
+ export interface FileStat {
80
+ fileType: string
81
+ len: number
82
+ isFile: boolean
83
+ isDir: boolean
84
+ }
85
+
86
+ export interface OpenModeJs {
87
+ read?: boolean
88
+ write?: boolean
89
+ create?: boolean
90
+ createNew?: boolean
91
+ truncate?: boolean
92
+ append?: boolean
93
+ }
94
+
95
+ export declare function runConformance(factory: (arg: [VfsQuotaJs]) => Promise<JsVfsHandle>): Promise<unknown>
96
+
97
+ export interface SandboxStats {
98
+ commandsRun: number
99
+ vfs?: VfsStatsJs
100
+ }
101
+
102
+ export interface VfsCallbackError {
103
+ code?: string
104
+ message?: string
105
+ }
106
+
107
+ export interface VfsQuotaJs {
108
+ maxBytes: number
109
+ maxFiles: number
110
+ maxFileSize: number
111
+ }
112
+
113
+ export interface VfsRequest {
114
+ path?: string
115
+ from?: string
116
+ to?: string
117
+ mode?: OpenModeJs
118
+ handle?: number
119
+ offset?: number
120
+ len?: number
121
+ data?: Buffer
122
+ }
123
+
124
+ export interface VfsResponse {
125
+ fileType?: string
126
+ len?: number
127
+ entries?: Array<DirEntryJs>
128
+ handle?: number
129
+ bytesRead?: number
130
+ bytesWritten?: number
131
+ data?: Buffer
132
+ usedBytes?: number
133
+ fileCount?: number
134
+ error?: VfsCallbackError
135
+ }
136
+
137
+ export interface VfsStatsJs {
138
+ usedBytes: number
139
+ fileCount: number
140
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@tinysandbox/tinysandbox",
3
+ "version": "0.3.0",
4
+ "description": "Node.js bindings for the tinysandbox in-process AI agent sandbox",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "scripts": {
8
+ "build": "napi build --platform --release --js native.cjs --dts native.d.ts",
9
+ "test": "npm run build && node --import tsx --test __test__/*.mjs",
10
+ "examples": "tsx examples/quickstart.ts && tsx examples/custom_command.ts && tsx examples/js_scripts.ts && tsx examples/js_vfs.ts"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/danthegoodman1/tinysandbox.git",
15
+ "directory": "tinysandbox-node"
16
+ },
17
+ "keywords": [
18
+ "sandbox",
19
+ "agent",
20
+ "shell",
21
+ "vfs",
22
+ "quickjs"
23
+ ],
24
+ "author": "Dan Goodman",
25
+ "license": "MIT OR Apache-2.0",
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "bugs": {
30
+ "url": "https://github.com/danthegoodman1/tinysandbox/issues"
31
+ },
32
+ "homepage": "https://github.com/danthegoodman1/tinysandbox#readme",
33
+ "devDependencies": {
34
+ "@napi-rs/cli": "^3.7.2",
35
+ "@types/node": "^26.1.0",
36
+ "tsx": "^4.23.0"
37
+ },
38
+ "files": [
39
+ "index.js",
40
+ "index.d.ts",
41
+ "native.cjs",
42
+ "native.d.ts",
43
+ "*.node"
44
+ ],
45
+ "napi": {
46
+ "binaryName": "tinysandbox-node",
47
+ "packageName": "tinysandbox"
48
+ }
49
+ }