| name | bun-fs-helpers |
| description | Pure Bun-native filesystem utilities from @side-quest/core/fs. Use when you need command-injection-safe filesystem operations, prefer Bun over node:fs, or want token-efficient fs helpers. All functions use Bun.spawn, Bun.file(), or Bun.write() - no node:fs dependencies. |
Bun Filesystem Helpers
Pure Bun-native filesystem utilities from @side-quest/core/fs - zero node:fs dependencies, command-injection safe.
When to Use
- Writing new code - Always prefer these over node:fs
- Command injection concerns - All shell commands use array args (safe)
- Token efficiency - Smaller imports, faster operations
- Bun-first projects - Leverages Bun's native APIs
Available Functions
File Existence
import { pathExists, pathExistsSync } from "@side-quest/core/fs";
if (await pathExists("/path/to/file")) { }
if (pathExistsSync("/path/to/file")) { }
Implementation: Uses Bun.file().exists() (async) or test -e command (sync)
Reading Files
import { readTextFile, readTextFileSync, readJsonFile, readJsonFileSync } from "@side-quest/core/fs";
const content = await readTextFile("/path/to/file.txt");
const content = readTextFileSync("/path/to/file.txt");
const data = await readJsonFile<MyType>("/path/to/data.json");
const data = readJsonFileSync<MyType>("/path/to/data.json");
Implementation: Uses Bun.file().text() (async) or cat command (sync)
Writing Files
import { writeTextFile, writeTextFileSync, writeJsonFile, writeJsonFileSync } from "@side-quest/core/fs";
await writeTextFile("/path/to/file.txt", "content");
writeTextFileSync("/path/to/file.txt", "content");
await writeJsonFile("/path/to/data.json", { foo: "bar" }, 2);
writeJsonFileSync("/path/to/data.json", { foo: "bar" }, 2);
Implementation: Uses Bun.write() (async) or printf via shell (sync)
Directory Operations
import { ensureDir, ensureDirSync, readDir, readDirAsync } from "@side-quest/core/fs";
await ensureDir("/path/to/nested/dir");
ensureDirSync("/path/to/nested/dir");
const files = readDir("/path/to/dir");
const files = await readDirAsync("/path/to/dir");
Implementation: Uses mkdir -p command, ls -1 command
File Operations
import { copyFile, moveFile, rename, unlink, unlinkSync } from "@side-quest/core/fs";
await copyFile("/source.txt", "/dest.txt");
await moveFile("/source.txt", "/dest.txt");
await rename("/old-path.txt", "/new-path.txt");
await unlink("/path/to/file.txt");
unlinkSync("/path/to/file.txt");
Implementation: Uses Bun.write() for copy, mv command for rename, rm command for delete
File Stats
import { stat } from "@side-quest/core/fs";
const stats = await stat("/path/to/file.txt");
console.log(stats.size);
console.log(stats.mtimeMs);
Implementation: Uses Bun.file().size and Bun.file().lastModified
Use case: TOCTOU protection - check file before AND after operations
Hashing
import { sha256, sha256File, fastHash } from "@side-quest/core/fs";
const hash = sha256("content");
const hash = await sha256File("/path/to/file.pdf");
const hash = fastHash("content");
Implementation: Uses Bun.CryptoHasher (SHA256) or Bun.hash (xxHash64)
Deep Equality
import { deepEquals } from "@side-quest/core/fs";
const equal = deepEquals(obj1, obj2);
const equal = deepEquals(obj1, obj2, true);
Implementation: Uses Bun.deepEquals()
Security Guarantees
✅ Command injection safe - All shell commands use array arguments:
Bun.spawnSync(["mkdir", "-p", userInput]);
Bun.spawnSync(`mkdir -p ${userInput}`);
✅ TOCTOU protection - Use stat() before AND after file operations to detect tampering
Migration from node:fs
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
existsSync("/path");
mkdirSync("/path", { recursive: true });
readFileSync("/path", "utf8");
writeFileSync("/path", "content", "utf8");
import { pathExistsSync, ensureDirSync, readTextFileSync, writeTextFileSync } from "@side-quest/core/fs";
pathExistsSync("/path");
ensureDirSync("/path");
readTextFileSync("/path");
writeTextFileSync("/path", "content");
Performance Characteristics
| Operation | Speed | Notes |
|---|
pathExists | ~1ms | Bun.file().exists() |
pathExistsSync | ~2ms | test -e command |
readTextFile | ~5-50ms | Depends on file size |
writeTextFile | ~5-20ms | Bun.write() is fast |
ensureDir | ~10ms | mkdir -p command |
readDir | ~5-15ms | ls -1 command |
sha256File | ~50-500ms | Depends on file size |
fastHash | <1ms | xxHash64 is very fast |
Common Patterns
Atomic File Updates
import { pathExistsSync, writeTextFileSync, rename } from "@side-quest/core/fs";
const tempPath = `${targetPath}.tmp`;
writeTextFileSync(tempPath, newContent);
await rename(tempPath, targetPath);
Safe File Modification with TOCTOU Protection
import { stat, readTextFileSync, writeTextFileSync } from "@side-quest/core/fs";
const preStat = await stat(filePath);
const content = readTextFileSync(filePath);
const modified = transform(content);
const postStat = await stat(filePath);
if (postStat.mtimeMs !== preStat.mtimeMs) {
throw new Error("File was modified during read (TOCTOU attack detected)");
}
writeTextFileSync(filePath, modified);
Idempotent Processing with Content Hashing
import { sha256File, pathExistsSync } from "@side-quest/core/fs";
const hash = await sha256File(sourceFile);
const processedMarker = `.processed/${hash}`;
if (pathExistsSync(processedMarker)) {
console.log("Already processed - skipping");
return;
}
writeTextFileSync(processedMarker, new Date().toISOString());
Implementation Details
All functions are exported from core/src/fs/index.ts and use:
Bun.file() - File existence, reading, size, timestamps
Bun.write() - Writing files
Bun.spawn() / Bun.spawnSync() - Shell commands with array args
Bun.CryptoHasher - SHA256 hashing
Bun.hash - Fast non-cryptographic hashing (xxHash64)
Bun.deepEquals() - Deep equality checks
Bun.Glob - Recursive file scanning (used elsewhere, not exported from fs)
Zero node:fs dependencies in production code.
References
- Core package source:
core/src/fs/index.ts
- Test coverage: 423 tests passing
- Used by: para-obsidian inbox processing, registry, PDF processor