@hyperfrontend/project-scope/vfsVFS Module
The vfs (Virtual File System) module provides a transactional file system abstraction for safely creating, updating, and deleting files. Changes are buffered in memory and can be committed atomically or rolled back.
Capabilities
Creating a Tree
Create a virtual file system tree backed by a real directory.
import { createTree, createTreeFromDisk } from '@hyperfrontend/project-scope'
// Create tree from directory
const tree = createTree('./my-project')
// With options
const tree2 = createTree('./my-project', {
verbose: true, // Enable detailed logging
followSymlinks: true, // Follow symlinks (default: true)
})
// Alias for createTree
const tree3 = createTreeFromDisk('./my-project')
Reading Files
Read files from the tree (considers buffered changes).
// Read as Buffer
const buffer = tree.read('src/index.ts')
// Read as string with encoding
const content = tree.read('src/index.ts', 'utf-8')
// Returns null if file doesn't exist
const maybeContent = tree.read('missing.ts', 'utf-8')
if (maybeContent === null) {
console.log('File not found')
}
Writing Files
Write files to the tree (buffered in memory).
import { Mode } from '@hyperfrontend/project-scope'
// Write/overwrite file
tree.write('src/new-file.ts', 'export const hello = "world"')
// Write with options
tree.write('scripts/build.sh', '#!/bin/bash\necho "Building..."', {
mode: Mode.ExclusiveCreate, // Fail if file exists
permissions: 0o755, // Set executable permission
})
// Write modes
tree.write('file.ts', content, { mode: Mode.Overwrite }) // Default: overwrite
tree.write('file.ts', content, { mode: Mode.ExclusiveCreate }) // Fail if exists
tree.write('file.ts', content, { mode: Mode.SkipIfExists }) // Skip if exists
Checking File Existence
// Check if file exists (considers pending changes)
if (tree.exists('src/index.ts')) {
// File exists
}
// Works with directories too
if (tree.exists('src/utils')) {
// Directory exists
}
Deleting Files
// Delete a file
tree.delete('src/old-file.ts')
// Delete a directory (and contents)
tree.delete('src/deprecated')
Renaming/Moving Files
// Rename file
tree.rename('src/old-name.ts', 'src/new-name.ts')
// Move file to different directory
tree.rename('src/utils/helper.ts', 'src/lib/helper.ts')
Listing Changes
// Get all pending changes
const changes = tree.listChanges()
for (const change of changes) {
console.log(`${change.type}: ${change.path}`)
// 'CREATE: src/new-file.ts'
// 'UPDATE: src/index.ts'
// 'DELETE: src/old-file.ts'
}
Committing Changes
Write all buffered changes to disk.
import { commitChanges } from '@hyperfrontend/project-scope'
// Preview changes (dry run)
const preview = commitChanges(tree, { dryRun: true })
console.log('Would create:', preview.created, 'files')
console.log('Would update:', preview.updated, 'files')
console.log('Would delete:', preview.deleted, 'files')
// Actually commit changes
const result = commitChanges(tree)
console.log('Created:', result.created)
console.log('Updated:', result.updated)
console.log('Deleted:', result.deleted)
// Verbose commit
commitChanges(tree, { verbose: true })
// Output:
// + src/new-file.ts
// ~ src/index.ts
// - src/old-file.ts
Rolling Back Changes
Discard all pending changes.
import { rollbackChanges } from '@hyperfrontend/project-scope'
// Make some changes
tree.write('src/temp.ts', 'content')
tree.delete('src/important.ts')
// Oops, discard all changes
rollbackChanges(tree)
// Or equivalently:
tree.clearChanges()
Generating Diffs
Generate unified diffs for file changes.
import { generateDiff, generateAllDiffs } from '@hyperfrontend/project-scope'
// Generate diff for a single change
const change = tree.listChanges()[0]
const diff = generateDiff(change)
console.log(`${diff.path}: +${diff.additions} -${diff.deletions}`)
for (const line of diff.lines) {
const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' '
console.log(`${prefix} ${line.content}`)
}
// Generate diffs for all changes
const allDiffs = generateAllDiffs(tree)
for (const diff of allDiffs) {
console.log(`\n--- ${diff.path}`)
// ... print diff lines
}
Tree Interface
The Tree interface:
interface Tree {
/** Workspace root path */
readonly root: string
/** Read file contents */
read(filePath: string): Buffer | null
read(filePath: string, encoding: BufferEncoding): string | null
/** Write file contents */
write(filePath: string, content: Buffer | string, options?: WriteOptions): void
/** Check if file/directory exists */
exists(filePath: string): boolean
/** Delete file or directory */
delete(filePath: string): void
/** Rename/move file or directory */
rename(from: string, to: string): void
/** Check if path is a file */
isFile(filePath: string): boolean
/** List directory children */
children(dirPath: string): string[]
/** List all pending changes */
listChanges(): FileChange[]
/** Clear all pending changes */
clearChanges(): void
}
File Change Types
interface FileChange {
/** Relative path from tree root */
path: string
/** Type of change */
type: 'CREATE' | 'UPDATE' | 'DELETE'
/** New content (undefined for DELETE) */
content?: Buffer
/** Original content (for UPDATE, enables diff) */
originalContent?: Buffer
/** File permissions */
mode?: number
}
Write Modes
const Mode = {
/** Overwrite existing files (default) */
Overwrite: 'overwrite',
/** Fail if file already exists */
ExclusiveCreate: 'exclusive',
/** Skip writing if file exists */
SkipIfExists: 'skip',
}
Security Features
Path Traversal Protection
The VFS prevents path traversal attacks:
// This will throw an error
tree.read('../../../etc/passwd')
// Error: Path escapes tree root: ../../../etc/passwd
Symlink Handling
Control symlink behavior:
// Symlinks followed by default
const tree1 = createTree('./', { followSymlinks: true })
// Disable symlink following (operations on symlinks will throw)
const tree2 = createTree('./', { followSymlinks: false })
Symlinks pointing outside the tree root are always rejected.
Design Principles
- Transactional: All changes are buffered until commit
- Safe: Path traversal and symlink escapes are prevented
- Atomic: Commit applies all changes or fails completely
- Reversible: Changes can be rolled back before commit
API Reference
ƒ Functions
Parameters
Returns
CommitResultExample
Committing changes to disk
import { createTree, commitChanges } from '@hyperfrontend/project-scope'
const tree = createTree('./my-project')
tree.write('src/new-file.ts', 'export const hello = "world"')
// Preview changes without writing
const preview = commitChanges(tree, { dryRun: true })
console.log('Would create:', preview.created, 'files')
// Actually commit changes
const result = commitChanges(tree)
console.log('Created:', result.created, 'Updated:', result.updated)Supports transactional modifications - all changes are buffered in memory and can be committed atomically or rolled back.
Parameters
Returns
TreeExample
Creating and using a file system tree
import { createFsTree, commitChanges } from '@hyperfrontend/project-scope'
const tree = createFsTree('/workspace')
tree.write('src/new-file.ts', 'export const value = 42')
tree.delete('old-file.ts')
commitChanges(tree) // Atomically apply all changes to diskParameters
Returns
TreeExample
Creating a virtual tree
import { createTree } from '@hyperfrontend/project-scope'
const tree = createTree('./my-project')
// Read files from tree
const content = tree.read('src/index.ts', 'utf-8')
// Make changes (buffered in memory)
tree.write('src/new-file.ts', 'export const x = 1')
// List pending changes
console.log(tree.listChanges())Convenience alias for createTree.
Parameters
Returns
TreeExample
Creating a tree from disk
import { createTreeFromDisk } from '@hyperfrontend/project-scope'
const tree = createTreeFromDisk('/path/to/project', { verbose: true })
const files = tree.children('/src')Parameters
| Name | Type | Description |
|---|---|---|
§diff | FileDiff | FileDiff to format |
Returns
stringExample
Formatting a unified diff
const formatted = formatUnifiedDiff(diff)
console.log(formatted)
// --- a/src/app.ts
// +++ b/src/app.ts
// @@ -1,3 +1,3 @@
// const x = 1;
// -const y = 2;
// +const y = 3;
// const z = 4;Parameters
Returns
FileDiff[]Example
Generating diffs for all changes
const tree = createTree('/workspace')
tree.write('new.txt', 'hello')
tree.delete('old.txt')
const diffs = generateAllDiffs(tree)
for (const diff of diffs) {
console.log(formatUnifiedDiff(diff))
}Parameters
Returns
FileDiffExample
Generating a diff for a file change
const change: FileChange = {
path: 'src/app.ts',
type: 'UPDATE',
originalContent: Buffer.from('const x = 1;\n'),
content: Buffer.from('const x = 2;\n'),
}
const diff = generateDiff(change)
// { path: 'src/app.ts', additions: 1, deletions: 1, lines: [...] }Parameters
| Name | Type | Description |
|---|---|---|
§tree | Tree | Virtual file system tree |
Example
Rolling back pending changes
import { createTree, rollbackChanges } from '@hyperfrontend/project-scope'
const tree = createTree('/workspace')
tree.write('new-file.ts', 'content')
// Decide to abort - discard all pending changes
rollbackChanges(tree)