diff --git a/README.md b/README.md
index 7d0f3e4..9edc56c 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
-
+
# `Capsule`
diff --git a/crates/capsule-cli/Cargo.lock b/crates/capsule-cli/Cargo.lock
index 5ca96fa..3739e4e 100644
--- a/crates/capsule-cli/Cargo.lock
+++ b/crates/capsule-cli/Cargo.lock
@@ -322,7 +322,7 @@ dependencies = [
[[package]]
name = "capsule-core"
-version = "0.8.9"
+version = "0.8.10"
dependencies = [
"anyhow",
"blake3",
@@ -351,7 +351,7 @@ dependencies = [
[[package]]
name = "capsule-run"
-version = "0.8.9"
+version = "0.8.10"
dependencies = [
"capsule-core",
"clap",
diff --git a/crates/capsule-cli/Cargo.toml b/crates/capsule-cli/Cargo.toml
index 082f474..5ab5d65 100644
--- a/crates/capsule-cli/Cargo.toml
+++ b/crates/capsule-cli/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "capsule-run"
-version = "0.8.9"
+version = "0.8.10"
edition = "2024"
description = "Secure WASM runtime to execute untrusted code"
license = "Apache-2.0"
@@ -16,7 +16,7 @@ path = "src/main.rs"
[dependencies]
clap = { version = "4.5.53", features = ["derive"] }
-capsule-core = { version= "0.8.9", path = "../capsule-core" }
+capsule-core = { version= "0.8.10", path = "../capsule-core" }
tokio = { version = "1.48.0", features = ["rt", "rt-multi-thread", "macros", "io-util", "sync"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
diff --git a/crates/capsule-cli/npm/package.json b/crates/capsule-cli/npm/package.json
index 5136012..2961e6e 100644
--- a/crates/capsule-cli/npm/package.json
+++ b/crates/capsule-cli/npm/package.json
@@ -1,6 +1,6 @@
{
"name": "@capsule-run/cli",
- "version": "0.8.9",
+ "version": "0.8.10",
"description": "Secure WASM runtime to execute untrusted code",
"bin": {
"capsule": "./bin/capsule.js"
@@ -29,9 +29,9 @@
"node": ">=18"
},
"optionalDependencies": {
- "@capsule-run/cli-darwin-arm64": "0.8.9",
- "@capsule-run/cli-darwin-x64": "0.8.9",
- "@capsule-run/cli-linux-x64": "0.8.9",
- "@capsule-run/cli-win32-x64": "0.8.9"
+ "@capsule-run/cli-darwin-arm64": "0.8.10",
+ "@capsule-run/cli-darwin-x64": "0.8.10",
+ "@capsule-run/cli-linux-x64": "0.8.10",
+ "@capsule-run/cli-win32-x64": "0.8.10"
}
}
diff --git a/crates/capsule-cli/pyproject.toml b/crates/capsule-cli/pyproject.toml
index 4f7ccba..28bbd93 100644
--- a/crates/capsule-cli/pyproject.toml
+++ b/crates/capsule-cli/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "capsule-run"
-version = "0.8.9"
+version = "0.8.10"
description = "Secure WASM runtime to execute untrusted code"
readme = "docs/README-pypi.md"
license = {text = "Apache-2.0"}
diff --git a/crates/capsule-core/Cargo.lock b/crates/capsule-core/Cargo.lock
index 69ff3cd..594c72b 100644
--- a/crates/capsule-core/Cargo.lock
+++ b/crates/capsule-core/Cargo.lock
@@ -302,7 +302,7 @@ dependencies = [
[[package]]
name = "capsule-core"
-version = "0.8.9"
+version = "0.8.10"
dependencies = [
"anyhow",
"blake3",
diff --git a/crates/capsule-core/Cargo.toml b/crates/capsule-core/Cargo.toml
index bd2cdd5..cd51e97 100644
--- a/crates/capsule-core/Cargo.toml
+++ b/crates/capsule-core/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "capsule-core"
-version = "0.8.9"
+version = "0.8.10"
edition = "2024"
description = "Core library for Capsule"
license = "Apache-2.0"
diff --git a/crates/capsule-sdk/javascript/package-lock.json b/crates/capsule-sdk/javascript/package-lock.json
index 9b7c4c0..dba17b4 100644
--- a/crates/capsule-sdk/javascript/package-lock.json
+++ b/crates/capsule-sdk/javascript/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@capsule-run/sdk",
- "version": "0.8.9",
+ "version": "0.8.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@capsule-run/sdk",
- "version": "0.8.9",
+ "version": "0.8.10",
"license": "Apache-2.0",
"dependencies": {
"@bytecodealliance/jco": "^1.0.0",
diff --git a/crates/capsule-sdk/javascript/package.json b/crates/capsule-sdk/javascript/package.json
index 74f0413..b195501 100644
--- a/crates/capsule-sdk/javascript/package.json
+++ b/crates/capsule-sdk/javascript/package.json
@@ -1,6 +1,6 @@
{
"name": "@capsule-run/sdk",
- "version": "0.8.9",
+ "version": "0.8.10",
"description": "Capsule JavaScript SDK - Execute untrusted code in secure WASM sandboxes",
"type": "module",
"main": "./dist/index.js",
diff --git a/crates/capsule-sdk/javascript/src/polyfills/fs.ts b/crates/capsule-sdk/javascript/src/polyfills/fs.ts
index a073bc2..762c680 100644
--- a/crates/capsule-sdk/javascript/src/polyfills/fs.ts
+++ b/crates/capsule-sdk/javascript/src/polyfills/fs.ts
@@ -10,9 +10,18 @@ declare const globalThis: {
'wasi:filesystem/preopens': any;
};
+interface WasiDatetime {
+ seconds: bigint;
+ nanoseconds: number;
+}
+
interface DescriptorStat {
+ type: string;
+ linkCount: bigint;
size: bigint;
- type?: string;
+ dataAccessTimestamp?: WasiDatetime | null;
+ dataModificationTimestamp?: WasiDatetime | null;
+ statusChangeTimestamp?: WasiDatetime | null;
}
interface DirectoryEntry {
@@ -28,6 +37,7 @@ interface Descriptor {
read(length: bigint, offset: bigint): [Uint8Array, boolean];
write(buffer: Uint8Array, offset: bigint): bigint;
stat(): DescriptorStat;
+ statAt(pathFlags: { symlinkFollow?: boolean }, path: string): DescriptorStat;
readDirectory(): DirectoryStream;
unlinkFileAt(path: string): void;
removeDirectoryAt(path: string): void;
@@ -38,6 +48,8 @@ interface Descriptor {
openFlags: { create?: boolean; directory?: boolean; exclusive?: boolean; truncate?: boolean },
descriptorFlags: { read?: boolean; write?: boolean; mutateDirectory?: boolean }
): Descriptor;
+ readlinkAt(path: string): string;
+ symlinkAt(oldPath: string, newPath: string): void;
}
interface PreopenedDir {
@@ -276,14 +288,39 @@ type Encoding = 'utf8' | 'utf-8' | 'buffer' | null | undefined;
interface ReadFileOptions {
encoding?: Encoding;
+ flag?: string;
}
interface WriteFileOptions {
encoding?: Encoding;
+ flag?: string;
+ mode?: number;
+}
+
+interface ReaddirOptions {
+ encoding?: Encoding;
+ withFileTypes?: boolean;
+ recursive?: boolean;
+}
+
+export class Dirent {
+ name: string;
+ private _type: string;
+ constructor(name: string, type: string) {
+ this.name = name;
+ this._type = type;
+ }
+ isFile() { return this._type === 'regular-file'; }
+ isDirectory() { return this._type === 'directory'; }
+ isSymbolicLink() { return this._type === 'symbolic-link'; }
+ isFIFO() { return this._type === 'fifo'; }
+ isBlockDevice() { return this._type === 'block-device'; }
+ isCharacterDevice() { return this._type === 'character-device'; }
+ isSocket() { return this._type === 'socket'; }
}
/**
- * Read file contents (async/callback style)
+ * Read file contents
*/
export function readFile(
path: string,
@@ -314,7 +351,7 @@ export function readFile(
}
/**
- * Write file contents (async/callback style)
+ * Write file contents
*/
export function writeFile(
path: string,
@@ -335,17 +372,67 @@ export function writeFile(
}
/**
- * Read directory contents (async/callback style)
+ * Read directory contents
*/
export function readdir(
path: string,
- optionsOrCallback: any | ((err: Error | null, files?: string[]) => void),
- callback?: (err: Error | null, files?: string[]) => void
+ optionsOrCallback: ReaddirOptions | ((err: Error | null, files?: string[] | Dirent[]) => void),
+ callback?: (err: Error | null, files?: string[] | Dirent[]) => void
): void {
+ const options: ReaddirOptions = typeof optionsOrCallback === 'function' ? {} : (optionsOrCallback as ReaddirOptions) ?? {};
const cb = typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
- list(path)
- .then((files) => cb?.(null, files))
+ if (options.withFileTypes) {
+ listWithTypes(path)
+ .then((entries) => cb?.(null, entries))
+ .catch((err) => cb?.(err instanceof Error ? err : new Error(String(err))));
+ } else {
+ list(path)
+ .then((files) => cb?.(null, files))
+ .catch((err) => cb?.(err instanceof Error ? err : new Error(String(err))));
+ }
+}
+
+/**
+ * stat callback
+ */
+export function stat(
+ path: string,
+ callback: (err: Error | null, stats?: StatResult) => void
+): void {
+ Promise.resolve()
+ .then(() => statSync(path))
+ .then((s) => callback(null, s))
+ .catch((err) => callback(err instanceof Error ? err : new Error(String(err))));
+}
+
+/**
+ * lstat callback
+ */
+export function lstat(
+ path: string,
+ callback: (err: Error | null, stats?: StatResult) => void
+): void {
+ Promise.resolve()
+ .then(() => lstatSync(path))
+ .then((s) => callback(null, s))
+ .catch((err) => callback(err instanceof Error ? err : new Error(String(err))));
+}
+
+/**
+ * appendFile callback
+ */
+export function appendFile(
+ path: string,
+ data: string | Uint8Array,
+ optionsOrCallback: WriteFileOptions | Encoding | ((err: Error | null) => void),
+ callback?: (err: Error | null) => void
+): void {
+ const cb: ((err: Error | null) => void) | undefined =
+ typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
+ Promise.resolve()
+ .then(() => appendFileSync(path, data))
+ .then(() => cb?.(null))
.catch((err) => cb?.(err instanceof Error ? err : new Error(String(err))));
}
@@ -425,9 +512,9 @@ export function appendFileSync(path: string, data: string | Uint8Array, _options
/**
* Read directory contents synchronously.
*/
-export function readdirSync(path: string, _options?: any): string[] {
+async function listWithTypes(path: string): Promise {
const resolved = resolvePath(path);
- if (!resolved) throw enoent(path);
+ if (!resolved) throw Object.assign(new Error(`ENOENT: no such file or directory, scandir '${path}'`), { code: 'ENOENT' });
try {
let targetDir = resolved.dir;
@@ -440,13 +527,45 @@ export function readdirSync(path: string, _options?: any): string[] {
);
}
const stream = targetDir.readDirectory();
- const entries: string[] = [];
- let entry;
+ const entries: Dirent[] = [];
+ let entry: DirectoryEntry | null | undefined;
while ((entry = stream.readDirectoryEntry()) && entry) {
- if (entry.name) entries.push(entry.name);
+ if (entry.name) entries.push(new Dirent(entry.name, entry.type ?? 'unknown'));
}
return entries;
- } catch (e) {
+ } catch {
+ throw Object.assign(new Error(`ENOENT: no such file or directory, scandir '${path}'`), { code: 'ENOENT' });
+ }
+}
+
+export function readdirSync(path: string, options?: ReaddirOptions): string[] | Dirent[] {
+ const resolved = resolvePath(path);
+ if (!resolved) throw enoent(path);
+
+ try {
+ let targetDir = resolved.dir;
+ if (resolved.relativePath !== '.') {
+ targetDir = resolved.dir.openAt(
+ { symlinkFollow: false },
+ resolved.relativePath,
+ { directory: true },
+ { read: true }
+ );
+ }
+ const stream = targetDir.readDirectory();
+ const names: string[] = [];
+ const dirents: Dirent[] = [];
+ let entry: DirectoryEntry | null | undefined;
+ while ((entry = stream.readDirectoryEntry()) && entry) {
+ if (!entry.name) continue;
+ if (options?.withFileTypes) {
+ dirents.push(new Dirent(entry.name, entry.type ?? 'unknown'));
+ } else {
+ names.push(entry.name);
+ }
+ }
+ return options?.withFileTypes ? dirents : names;
+ } catch {
throw Object.assign(new Error(`ENOENT: no such file or directory, scandir '${path}'`), { code: 'ENOENT' });
}
}
@@ -455,48 +574,105 @@ export interface StatResult {
isFile: () => boolean;
isDirectory: () => boolean;
isSymbolicLink: () => boolean;
+ isFIFO: () => boolean;
+ isBlockDevice: () => boolean;
+ isCharacterDevice: () => boolean;
+ isSocket: () => boolean;
+ dev: number;
+ ino: number;
+ mode: number;
+ nlink: number;
+ uid: number;
+ gid: number;
+ rdev: number;
size: number;
- mtimeMs: number;
+ blksize: number;
+ blocks: number;
+ // timestamp (ms)
atimeMs: number;
+ mtimeMs: number;
ctimeMs: number;
- mode: number;
+ birthtimeMs: number;
+ // timestamp (Date) — used by glob, fast-glob, chokidar etc.
+ atime: Date;
+ mtime: Date;
+ ctime: Date;
+ birthtime: Date;
}
-function makeStatResult(type: 'file' | 'directory' | 'notfound', size: bigint = BigInt(0)): StatResult {
+function datetimeToMs(dt: WasiDatetime | null | undefined): number {
+ if (!dt) return 0;
+ return Number(dt.seconds) * 1000 + Math.floor(dt.nanoseconds / 1_000_000);
+}
+
+function makeStatResult(s: DescriptorStat): StatResult {
+ const isDir = s.type === 'directory';
+ const isSym = s.type === 'symbolic-link';
+ const isFile = s.type === 'regular-file';
+ const mtimeMs = datetimeToMs(s.dataModificationTimestamp);
+ const atimeMs = datetimeToMs(s.dataAccessTimestamp);
+ const ctimeMs = datetimeToMs(s.statusChangeTimestamp);
+ const size = Number(s.size);
return {
- isFile: () => type === 'file',
- isDirectory: () => type === 'directory',
- isSymbolicLink: () => false,
- size: Number(size),
- mtimeMs: 0,
- atimeMs: 0,
- ctimeMs: 0,
- mode: type === 'directory' ? 0o40755 : 0o100644,
+ isFile: () => isFile,
+ isDirectory: () => isDir,
+ isSymbolicLink: () => isSym,
+ isFIFO: () => s.type === 'fifo',
+ isBlockDevice: () => s.type === 'block-device',
+ isCharacterDevice: () => s.type === 'character-device',
+ isSocket: () => s.type === 'socket',
+ dev: 0, ino: 0, nlink: isDir ? 2 : 1,
+ uid: 0, gid: 0, rdev: 0,
+ size,
+ blksize: 4096,
+ blocks: Math.ceil(size / 512),
+ mode: isDir ? 0o40755 : 0o100644,
+ atimeMs, mtimeMs, ctimeMs, birthtimeMs: mtimeMs,
+ atime: new Date(atimeMs),
+ mtime: new Date(mtimeMs),
+ ctime: new Date(ctimeMs),
+ birthtime: new Date(mtimeMs),
};
}
/**
- * Get file stats synchronously.
+ * Get file stats synchronously (follows symlinks).
*/
export function statSync(path: string): StatResult {
const resolved = resolvePath(path);
if (!resolved) throw enoent(path);
try {
- const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
- const s = fd.stat();
- const type = s.type === 'directory' ? 'directory' : 'file';
- return makeStatResult(type, s.size);
- } catch (e) {
+ if (typeof resolved.dir.statAt === 'function') {
+ const s = resolved.dir.statAt({ symlinkFollow: true }, resolved.relativePath);
+ return makeStatResult(s);
+ }
+
+ const fd = resolved.dir.openAt({ symlinkFollow: true }, resolved.relativePath, {}, { read: true });
+ return makeStatResult(fd.stat());
+ } catch {
throw Object.assign(new Error(`ENOENT: no such file or directory, stat '${path}'`), { code: 'ENOENT' });
}
}
/**
- * Get file stats synchronously (no symlink follow — same as stat in WASI 0.2).
+ * Get file stats synchronously without following symlinks (lstat).
*/
export function lstatSync(path: string): StatResult {
- return statSync(path);
+ const resolved = resolvePath(path);
+ if (!resolved) throw enoent(path);
+
+ try {
+ if (typeof resolved.dir.statAt === 'function') {
+ const s = resolved.dir.statAt({ symlinkFollow: false }, resolved.relativePath);
+ return makeStatResult(s);
+ }
+
+ const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
+ return makeStatResult(fd.stat());
+ } catch {
+ throw Object.assign(new Error(`ENOENT: no such file or directory, lstat '${path}'`), { code: 'ENOENT' });
+ }
}
/**
@@ -574,8 +750,10 @@ export function rmSync(path: string, options?: RmOptions): void {
}
try {
- const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
- const s = fd.stat();
+ const s = typeof resolved.dir.statAt === 'function'
+ ? resolved.dir.statAt({ symlinkFollow: false }, resolved.relativePath)
+ : (() => { const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true }); return fd.stat(); })();
+
if (s.type === 'directory') {
if (!options?.recursive) {
throw Object.assign(
@@ -681,15 +859,91 @@ export async function unlink(path: string): Promise {
}
/**
- * Returns 'file', 'directory', or 'notfound' for a given path.
+ * Read the target of a symlink synchronously.
+ */
+export function readlinkSync(path: string): string {
+ const resolved = resolvePath(path);
+ if (!resolved) throw enoent(path);
+ try {
+ if (typeof resolved.dir.readlinkAt !== 'function') {
+ throw Object.assign(
+ new Error(`ENOSYS: function not implemented, readlink '${path}'`),
+ { code: 'ENOSYS' }
+ );
+ }
+ return resolved.dir.readlinkAt(resolved.relativePath);
+ } catch (e) {
+ if (e instanceof Error && (e as any).code) throw e;
+ throw Object.assign(
+ new Error(`EINVAL: invalid argument, readlink '${path}'`),
+ { code: 'EINVAL' }
+ );
+ }
+}
+
+/**
+ * Read the target of a symlink asynchronously (callback style).
+ */
+export function readlink(
+ path: string,
+ callback: (err: Error | null, linkString?: string) => void
+): void {
+ Promise.resolve()
+ .then(() => readlinkSync(path))
+ .then((target) => callback(null, target))
+ .catch((err) => callback(err instanceof Error ? err : new Error(String(err))));
+}
+
+/**
+ * Create a symbolic link synchronously.
+ * target: the link destination (relative to the sandbox — cannot escape it).
+ * path: the new symlink path.
+ */
+export function symlinkSync(target: string, path: string): void {
+ const resolved = resolvePath(path);
+ if (!resolved) throw enoent(path);
+ try {
+ if (typeof resolved.dir.symlinkAt !== 'function') {
+ throw Object.assign(
+ new Error(`ENOSYS: function not implemented, symlink '${target}' -> '${path}'`),
+ { code: 'ENOSYS' }
+ );
+ }
+ resolved.dir.symlinkAt(target, resolved.relativePath);
+ } catch (e) {
+ if (e instanceof Error && (e as any).code) throw e;
+ throw Object.assign(
+ new Error(`ENOSYS: function not implemented, symlink '${target}' -> '${path}'`),
+ { code: 'ENOSYS' }
+ );
+ }
+}
+
+/**
+ * Create a symbolic link asynchronously (callback style).
+ */
+export function symlink(
+ target: string,
+ path: string,
+ callback: (err: Error | null) => void
+): void {
+ Promise.resolve()
+ .then(() => symlinkSync(target, path))
+ .then(() => callback(null))
+ .catch((err) => callback(err instanceof Error ? err : new Error(String(err))));
+}
+
+/**
+ * Internal helper — returns the entry type for a path without building a full StatResult.
*/
async function statPath(path: string): Promise<'file' | 'directory' | 'notfound'> {
const resolved = resolvePath(path);
if (!resolved) return 'notfound';
try {
- const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
- const s = fd.stat();
+ const s = typeof resolved.dir.statAt === 'function'
+ ? resolved.dir.statAt({ symlinkFollow: false }, resolved.relativePath)
+ : (() => { const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true }); return fd.stat(); })();
if (s.type === 'directory') return 'directory';
return 'file';
} catch {
@@ -881,8 +1135,8 @@ export const promises = {
}
},
- async readdir(path: string): Promise {
- return list(path);
+ async readdir(path: string, options?: ReaddirOptions): Promise {
+ return options?.withFileTypes ? listWithTypes(path) : list(path);
},
async access(path: string): Promise {
@@ -897,14 +1151,57 @@ export const promises = {
},
async stat(path: string): Promise {
- const kind = await statPath(path);
- if (kind === 'notfound') {
+ const resolved = resolvePath(path);
+ if (!resolved) {
+ throw Object.assign(
+ new Error(`ENOENT: no such file or directory, stat '${path}'`),
+ { code: 'ENOENT' }
+ );
+ }
+ try {
+ if (typeof resolved.dir.statAt === 'function') {
+ const s = resolved.dir.statAt({ symlinkFollow: true }, resolved.relativePath);
+ return makeStatResult(s);
+ }
+
+ const fd = resolved.dir.openAt({ symlinkFollow: true }, resolved.relativePath, {}, { read: true });
+ return makeStatResult(fd.stat());
+ } catch {
throw Object.assign(
new Error(`ENOENT: no such file or directory, stat '${path}'`),
{ code: 'ENOENT' }
);
}
- return makeStatResult(kind);
+ },
+
+ async lstat(path: string): Promise {
+ const resolved = resolvePath(path);
+ if (!resolved) throw Object.assign(new Error(`ENOENT: no such file or directory, lstat '${path}'`), { code: 'ENOENT' });
+ try {
+ if (typeof resolved.dir.statAt === 'function') {
+ return makeStatResult(resolved.dir.statAt({ symlinkFollow: false }, resolved.relativePath));
+ }
+ const fd = resolved.dir.openAt({ symlinkFollow: false }, resolved.relativePath, {}, { read: true });
+ return makeStatResult(fd.stat());
+ } catch {
+ throw Object.assign(new Error(`ENOENT: no such file or directory, lstat '${path}'`), { code: 'ENOENT' });
+ }
+ },
+
+ async readlink(path: string): Promise {
+ return readlinkSync(path);
+ },
+
+ async symlink(target: string, path: string): Promise {
+ symlinkSync(target, path);
+ },
+
+ async appendFile(path: string, data: string | Uint8Array): Promise {
+ appendFileSync(path, data);
+ },
+
+ async rename(oldPath: string, newPath: string): Promise {
+ renameSync(oldPath, newPath);
},
async rmdir(path: string, options?: RmdirOptions): Promise {
@@ -928,23 +1225,44 @@ export const promises = {
},
};
+export const constants = {
+ F_OK: 0,
+ R_OK: 4,
+ W_OK: 2,
+ X_OK: 1,
+ O_RDONLY: 0,
+ O_WRONLY: 1,
+ O_RDWR: 2,
+ O_CREAT: 64,
+ O_TRUNC: 512,
+ O_APPEND: 1024,
+};
+
const fs = {
// Async / callback
readFile,
writeFile,
+ appendFile,
readdir,
+ stat,
+ lstat,
+ readlink,
+ symlink,
unlink,
rmdir,
rm,
mkdir,
copyFile,
cp,
+ // Sync
readFileSync,
writeFileSync,
appendFileSync,
readdirSync,
statSync,
lstatSync,
+ readlinkSync,
+ symlinkSync,
mkdirSync,
rmdirSync,
rmSync,
@@ -955,6 +1273,9 @@ const fs = {
existsSync,
// Promises API
promises,
+ // Constants
+ constants,
+ Dirent,
};
export default fs;
diff --git a/crates/capsule-sdk/python/pyproject.toml b/crates/capsule-sdk/python/pyproject.toml
index 9816f53..c1411e0 100644
--- a/crates/capsule-sdk/python/pyproject.toml
+++ b/crates/capsule-sdk/python/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "capsule"
-version = "0.8.9"
+version = "0.8.10"
description = "Capsule Python SDK - run untrusted code in secure WASM sandboxes"
readme = "README.md"
requires-python = ">=3.10"