@hyperfrontend/project-scope/vfs

VFS 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

  1. Transactional: All changes are buffered until commit
  2. Safe: Path traversal and symlink escapes are prevented
  3. Atomic: Commit applies all changes or fails completely
  4. Reversible: Changes can be rolled back before commit

API Reference§

ƒ Functions

§function

commitChanges(tree: Tree, options?: CommitOptions): CommitResult

Commit buffered changes to disk.
Parameters
NameTypeDescription
§tree
TreeVirtual file system tree with pending modifications
§options?
CommitOptionsConfiguration for dry-run and verbosity
Returns
CommitResult
Result of the commit operation
Example

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)
§function

createFsTree(root: string, options?: CreateTreeOptions): Tree

Create a file system-backed tree implementation.
Supports transactional modifications - all changes are buffered in memory and can be committed atomically or rolled back.
Parameters
NameTypeDescription
§root
stringAbsolute path to the workspace root directory
§options?
CreateTreeOptionsConfiguration for verbose logging
Returns
Tree
A Tree instance
Example

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 disk
§function

createTree(root: string, options?: CreateTreeOptions): Tree

Create a virtual file system tree.
Parameters
NameTypeDescription
§root
stringRoot directory path
§options?
CreateTreeOptionsTree creation options
Returns
Tree
A new Tree instance
Example

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())
§function

createTreeFromDisk(root: string, options?: CreateTreeOptions): Tree

Create a tree from disk.
Convenience alias for createTree.
Parameters
NameTypeDescription
§root
stringRoot directory path
§options?
CreateTreeOptionsTree creation options
Returns
Tree
A new Tree instance backed by disk
Example

Creating a tree from disk

import { createTreeFromDisk } from '@hyperfrontend/project-scope'

const tree = createTreeFromDisk('/path/to/project', { verbose: true })
const files = tree.children('/src')
§function

formatUnifiedDiff(diff: FileDiff): string

Format a FileDiff as a unified diff string.
Parameters
NameTypeDescription
§diff
FileDiffFileDiff to format
Returns
string
Unified diff string
Example

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;
§function

generateAllDiffs(tree: Tree, options: DiffOptions): FileDiff[]

Generate diffs for all changes in a tree.
Parameters
NameTypeDescription
§tree
TreeTree to generate diffs for
§options
DiffOptionsDiff options(default: {})
Returns
FileDiff[]
Array of FileDiff objects
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))
}
§function

generateDiff(change: FileChange, options: DiffOptions): FileDiff

Generate a diff for a single file change.
Parameters
NameTypeDescription
§change
FileChangeFile change to diff
§options
DiffOptionsConfiguration for diff generation including context lines(default: {})
Returns
FileDiff
FileDiff object
Example

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: [...] }
§function

rollbackChanges(tree: Tree): void

Discard all pending changes.
Parameters
NameTypeDescription
§tree
TreeVirtual 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)

◈ Interfaces

§interface

CommitOptions

Options for committing changes.
Properties
§dryRun?:booleanDry run - don't actually write to disk
§verbose?:booleanEnable verbose logging
§interface

CommitResult

Result of committing changes.
Properties
§changes:FileChange[]List of all changes applied
§created:numberNumber of files created
§deleted:numberNumber of files deleted
§dryRun:booleanWhether this was a dry run
§updated:numberNumber of files updated
§interface

CreateTreeOptions

Options for creating a tree.
Properties
§verbose?:booleanEnable verbose logging
§interface

DiffLine

A single line in a diff.
Properties
§content:stringLine content
§line:numberLine number (in original for remove/context, in new for add)
§type:"remove" | "context" | "add"Type of line change
§interface

DiffOptions

Options for diff generation.
Properties
§contextLines?:numberNumber of context lines around changes.
§interface

FileChange

A single file change in the tree.
Properties
§content?:BufferFile content (undefined for DELETE)
§mode?:numberFile mode/permissions
§originalContent?:BufferOriginal content for UPDATE (enables diff)
§path:stringRelative path from tree root
§type:"CREATE" | "UPDATE" | "DELETE"Type of change
§interface

FileDiff

Diff for a single file.
Properties
§additions:numberNumber of added lines
§deletions:numberNumber of deleted lines
§lines:DiffLine[]Diff lines
§path:stringFile path
§interface

Tree

Virtual file system tree interface. Transactional in-memory tree of pending file operations.
Properties
§readonly root:stringWorkspace root path
§interface

WriteOptions

Options for write operations.
Properties
§mode?:ModeTypeWrite mode
§permissions?:numberFile permissions (e.g., 0o755)

◆ Types

§type

ModeType

Write mode type extracted from Mode object.
type ModeType = indexedAccess

● Variables

§type

Mode

Write mode for file operations.