
Security News
Lovable’s OJ Rewrites Vite’s Dev Server in Rust as AI Lowers the Cost of Forking Open Source
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.
github-filesystem
Advanced tools
A TypeScript library that allows you to interact with GitHub repositories like a file system. Supports both instant mode (direct writes to GitHub) and commit mode (stage changes in Upstash KV before committing).
writeFile, readFile, readdir, mkdir, deleteFilefs.promises APInpm install github-filesystem
You'll need:
Important: Credentials are read from environment variables:
GITHUB_TOKEN - GitHub personal access tokenUPSTASH_REDIS_REST_URL - Upstash Redis REST URLUPSTASH_REDIS_REST_TOKEN - Upstash Redis REST tokenimport { GitHubFS } from "github-filesystem";
// Credentials are automatically read from environment variables:
// - GITHUB_TOKEN
// - UPSTASH_REDIS_REST_URL
// - UPSTASH_REDIS_REST_TOKEN
const fs = new GitHubFS({
repo: "owner/repo-name",
namespace: "my-project", // Used to organize work sessions
branch: "main", // Optional, defaults to "main"
});
In instant mode, every operation immediately writes to GitHub:
// Write a file (creates a commit)
await fs.writeFile("docs/README.md", "# Hello World");
// Read a file
const content = await fs.readFile("docs/README.md");
console.log(content.toString());
// List directory contents
const entries = await fs.readdir("docs");
entries.forEach(entry => {
console.log(`${entry.name} (${entry.type})`);
});
// Create a directory
await fs.mkdir("src/components", { recursive: true });
// Delete a file (creates a commit)
await fs.deleteFile("old-file.txt");
In commit mode, changes are staged in Upstash KV and committed together:
// Start a work session
const sessionId = await fs.startWork();
console.log(`Started work session: ${sessionId}`);
// Make multiple changes (all staged in KV)
await fs.writeFile("file1.txt", "Content 1");
await fs.writeFile("file2.txt", "Content 2");
await fs.mkdir("new-folder", { recursive: true });
await fs.deleteFile("old-file.txt");
// Read operations check KV first, then GitHub
const content = await fs.readFile("file1.txt");
// Commit all changes as a single commit
await fs.commitWork("Add multiple files and reorganize structure");
Resume an interrupted work session:
// Try to resume the last work session for this namespace
const sessionId = await fs.resumeWork();
if (sessionId) {
console.log(`Resumed work session: ${sessionId}`);
// Continue making changes
await fs.writeFile("another-file.txt", "More content");
// Commit when done
await fs.commitWork("Complete the interrupted work");
} else {
console.log("No previous work session found");
}
Cancel a work session without committing:
await fs.startWork();
// Make some changes...
await fs.writeFile("temp.txt", "Temporary content");
// Decide not to commit
await fs.cancelWork(); // All staged changes are discarded
GitHubFS implements a Node.js fs-compatible API, making it a drop-in replacement for most file system operations (except for startWork/commitWork/cancelWork which are GitHubFS-specific).
All methods return Promises and work exactly like Node.js fs.promises:
import { GitHubFS } from "github-filesystem";
const fs = new GitHubFS({ repo: "owner/repo" });
// Read/write files
await fs.readFile("file.txt");
await fs.writeFile("file.txt", "content");
await fs.appendFile("file.txt", "more content");
// File operations
await fs.copyFile("src.txt", "dest.txt");
await fs.rename("old.txt", "new.txt");
await fs.unlink("file.txt");
// Directory operations
await fs.readdir("path/to/dir");
await fs.mkdir("new-dir", { recursive: true });
await fs.rmdir("dir", { recursive: true });
await fs.rm("file-or-dir", { recursive: true, force: true });
// File info
const stats = await fs.stat("file.txt");
console.log(stats.isFile(), stats.isDirectory(), stats.size);
await fs.access("file.txt"); // Check if file exists
const exists = await fs.exists("file.txt"); // Returns boolean
For compatibility with older code, callback-based methods are also available:
// Read file with callback
fs.readFileCallback("file.txt", (err, data) => {
if (err) throw err;
console.log(data.toString());
});
// Write file with callback
fs.writeFileCallback("file.txt", "content", (err) => {
if (err) throw err;
console.log("File written!");
});
// Read directory with callback
fs.readdirCallback("path", (err, files) => {
if (err) throw err;
console.log(files);
});
// Stat with callback
fs.statCallback("file.txt", (err, stats) => {
if (err) throw err;
console.log(stats.isFile());
});
// Other callback methods:
// mkdirCallback, unlinkCallback, rmdirCallback, existsCallback
Synchronous operations are not supported for remote filesystems and will throw an error:
try {
fs.readFileSync("file.txt"); // Throws error
} catch (error) {
console.error("Synchronous operations are not supported");
}
You can use GitHubFS as a drop-in replacement for fs.promises:
// Before (using Node.js fs)
import { promises as fs } from "fs";
// After (using GitHubFS)
import { GitHubFS } from "github-filesystem";
const fs = new GitHubFS({ repo: "owner/repo" });
// All your existing code works the same!
const content = await fs.readFile("file.txt");
await fs.writeFile("output.txt", content);
const files = await fs.readdir(".");
Note: The only difference is you need to use startWork()/commitWork() if you want to batch changes, otherwise each operation creates an immediate commit.
new GitHubFS(config: GitHubFSConfig)
Config Options:
repo: GitHub repository in format "owner/repo"namespace: Namespace for organizing work sessionsbranch: Git branch to work with (default: "main")Environment Variables (Required):
GITHUB_TOKEN: GitHub personal access tokenUPSTASH_REDIS_REST_URL: Upstash Redis REST URLUPSTASH_REDIS_REST_TOKEN: Upstash Redis REST tokenstartWork(): Promise<string>Start a work session (commit mode). Returns the session ID.
resumeWork(): Promise<string | null>Resume the last work session for this namespace. Returns the session ID if found, null otherwise.
commitWork(message: string): Promise<void>Commit all staged changes to GitHub with the given commit message.
cancelWork(): Promise<void>Cancel the current work session without committing changes.
getMode(): FSModeGet the current mode ("instant" or "commit").
getCurrentSessionId(): string | nullGet the current work session ID (if in commit mode).
readFile(path: string, options?: ReadFileOptions): Promise<Buffer>Read a file. In commit mode, checks KV first, then falls back to GitHub.
writeFile(path: string, content: Buffer | string, options?: WriteFileOptions): Promise<void>Write a file. In instant mode, creates a commit immediately. In commit mode, stages the change in KV.
appendFile(path: string, data: string | Buffer, options?: WriteFileOptions): Promise<void>Append to a file. Creates the file if it doesn't exist.
copyFile(src: string, dest: string): Promise<void>Copy a file from source to destination.
rename(oldPath: string, newPath: string): Promise<void>Rename or move a file.
unlink(path: string): Promise<void>Delete a file. Alias for deleteFile().
deleteFile(path: string): Promise<void>Delete a file. In instant mode, creates a commit immediately. In commit mode, stages the deletion.
readdir(path: string, options?: ReaddirOptions): Promise<DirEntry[] | string[]>List directory contents. Returns an array of entries with name, type, path, and optional sha/size.
Options:
withFileTypes: Return Dirent objects instead of strings (default: false)mkdir(path: string, options?: MkdirOptions): Promise<void>Create a directory. Since GitHub doesn't support empty directories, this creates a .gitkeep file.
Options:
recursive: Create parent directories if they don't exist (default: false)rmdir(path: string, options?: { recursive?: boolean }): Promise<void>Remove a directory. Alias for rm().
rm(path: string, options?: RmOptions): Promise<void>Remove a file or directory.
Options:
recursive: Remove directories and their contents recursively (default: false)force: Ignore errors if file doesn't exist (default: false)stat(path: string): Promise<Stats>Get file or directory statistics. Returns a Stats object with isFile(), isDirectory(), size, etc.
lstat(path: string): Promise<Stats>Get file or directory statistics (alias for stat(), no symlink support).
access(path: string, mode?: number): Promise<void>Check if a file exists and is accessible. Throws if not accessible.
exists(path: string): Promise<boolean>Check if a file or directory exists. Returns true or false.
All promise-based methods have callback equivalents:
readFileCallback(path, [options], callback)writeFileCallback(path, data, [options], callback)readdirCallback(path, [options], callback)statCallback(path, callback)mkdirCallback(path, [options], callback)unlinkCallback(path, callback)rmdirCallback(path, [options], callback)existsCallback(path, callback)These methods throw an error:
readFileSync(), writeFileSync(), readdirSync(), statSync(), mkdirSync(), unlinkSync(), rmdirSync(), existsSync(){namespace}/{sessionId}/{filepath}commitWork() creates a single commit with all changes{namespace}/sessionresumeWork() retrieves the last session for the namespaceconst fs = new GitHubFS({ /* config */ });
await fs.startWork();
// Update multiple documentation files
const files = ["README.md", "CONTRIBUTING.md", "LICENSE"];
for (const file of files) {
const content = await fs.readFile(file);
const updated = content.toString().replace("2023", "2024");
await fs.writeFile(file, updated);
}
await fs.commitWork("Update copyright year to 2024");
const fs = new GitHubFS({ /* config */ });
await fs.startWork();
try {
// Try some changes
await fs.writeFile("config.json", JSON.stringify(newConfig));
await fs.deleteFile("old-config.json");
// Test if everything works...
const valid = await validateConfig();
if (valid) {
await fs.commitWork("Update configuration");
} else {
await fs.cancelWork(); // Rollback
}
} catch (error) {
await fs.cancelWork(); // Rollback on error
throw error;
}
const fs = new GitHubFS({ /* config */ });
// Start or resume work
let sessionId = await fs.resumeWork();
if (!sessionId) {
sessionId = await fs.startWork();
}
// Process files incrementally
for (const file of largeFileList) {
await processAndWrite(fs, file);
// Can stop and resume later...
}
await fs.commitWork("Process all files");
Credentials are automatically loaded from environment variables. Set them in your .env file or environment:
# .env file
GITHUB_TOKEN=ghp_your_github_token
UPSTASH_REDIS_REST_URL=https://your-redis.upstash.io
UPSTASH_REDIS_REST_TOKEN=your_upstash_token
Then use the class:
const fs = new GitHubFS({
repo: process.env.GITHUB_REPO || "owner/repo",
namespace: process.env.NAMESPACE || "default",
});
try {
await fs.readFile("non-existent.txt");
} catch (error) {
console.error("File not found:", error);
}
try {
await fs.startWork();
await fs.startWork(); // Error: already in commit mode
} catch (error) {
console.error("Cannot start work:", error);
}
.gitkeep filesMIT
Contributions are welcome! Please open an issue or submit a pull request.
FAQs
Interact with GitHub repositories like a file system
We found that github-filesystem demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
Lovable’s OJ rewrites Vite’s dev server in Rust, reducing memory use and preview times as AI lowers the cost of open source reimplementation.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.