@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, following the same patterns as NX devkit's Tree interface.

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 mirrors NX devkit's Tree for compatibility:

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. Compatible: Mirrors NX devkit Tree interface
  4. Atomic: Commit applies all changes or fails completely
  5. 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
Tree
Virtual file system tree with pending modifications
§options?
CommitOptions
Configuration 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
string
Absolute path to the workspace root directory
§options?
CreateTreeOptions
Configuration 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
string
Root directory path
§options?
CreateTreeOptions
Tree 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
string
Root directory path
§options?
CreateTreeOptions
Tree 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
FileDiff
FileDiff 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
Tree
Tree to generate diffs for
§options
DiffOptions
Diff 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
FileChange
File change to diff
§options
DiffOptions
Configuration 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
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)

Interfaces

§interface

CommitOptions

Options for committing changes.

Properties

§dryRun?:boolean
Dry run - don't actually write to disk
§verbose?:boolean
Enable verbose logging
§interface

CommitResult

Result of committing changes.

Properties

§changes:FileChange[]
List of all changes applied
§created:number
Number of files created
§deleted:number
Number of files deleted
§dryRun:boolean
Whether this was a dry run
§updated:number
Number of files updated
§interface

CreateTreeOptions

Options for creating a tree.

Properties

§verbose?:boolean
Enable verbose logging
§interface

DiffLine

A single line in a diff.

Properties

§content:string
Line content
§line:number
Line 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?:number
Number of context lines around changes.
§interface

FileChange

A single file change in the tree.

Properties

§content?:Buffer
File content (undefined for DELETE)
§mode?:number
File mode/permissions
§originalContent?:Buffer
Original content for UPDATE (enables diff)
§path:string
Relative path from tree root
§type:"CREATE" | "UPDATE" | "DELETE"
Type of change
§interface

FileDiff

Diff for a single file.

Properties

§additions:number
Number of added lines
§deletions:number
Number of deleted lines
§lines:DiffLine[]
Diff lines
§path:string
File path
§interface

Tree

Virtual file system tree interface. Mirrors NX devkit Tree interface for compatibility.

Properties

§readonly root:string
Workspace root path
§interface

WriteOptions

Options for write operations.

Properties

§mode?:ModeType
Write mode
§permissions?:number
File 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.