Core virtual file system operations for reading, writing, and manipulating files and directories in a staging environment. Trees represent the complete state of a file system without side effects until committed through a sink.
The main interface for all file system operations within schematics.
/**
* Virtual file system tree interface for staging file operations
*/
interface Tree {
readonly root: DirEntry;
readonly actions: Action[];
// Structural operations
branch(): Tree;
merge(other: Tree, strategy?: MergeStrategy): void;
// Read operations
read(path: string): Buffer | null;
readText(path: string): string;
readJson(path: string): JsonValue;
exists(path: string): boolean;
get(path: string): FileEntry | null;
getDir(path: string): DirEntry;
visit(visitor: FileVisitor): void;
// Write operations
create(path: string, content: Buffer | string): void;
overwrite(path: string, content: Buffer | string): void;
delete(path: string): void;
rename(from: string, to: string): void;
// Advanced operations
beginUpdate(path: string): UpdateRecorder;
commitUpdate(record: UpdateRecorder): void;
apply(action: Action, strategy?: MergeStrategy): void;
}Usage Examples:
import { Tree } from "@angular-devkit/schematics";
function myRule(tree: Tree): Tree {
// Check if file exists
if (tree.exists('/package.json')) {
// Read and parse JSON
const packageJson = tree.readJson('/package.json') as any;
// Modify the content
packageJson.scripts = {
...packageJson.scripts,
'build': 'ng build'
};
// Write back to tree
tree.overwrite('/package.json', JSON.stringify(packageJson, null, 2));
} else {
// Create new file
tree.create('/package.json', JSON.stringify({
name: 'my-app',
version: '1.0.0'
}, null, 2));
}
return tree;
}Static methods for creating and manipulating trees.
interface TreeConstructor {
/** Create an empty tree */
empty(): Tree;
/** Create a branch (copy) of an existing tree */
branch(tree: Tree): Tree;
/** Merge two trees using specified strategy */
merge(tree: Tree, other: Tree, strategy?: MergeStrategy): Tree;
/** Partition tree into two based on predicate */
partition(tree: Tree, predicate: FilePredicate<boolean>): [Tree, Tree];
/** Optimize tree operations (identity function) */
optimize(tree: Tree): Tree;
}
const Tree: TreeConstructor;Specialized tree implementations for different use cases.
/**
* Tree implementation backed by a virtual file system host
*/
class HostTree implements Tree {
constructor(host?: virtualFs.Host);
readonly backend: virtualFs.ReadonlyHost;
}
/**
* Tree that delegates operations to another tree
*/
abstract class DelegateTree implements Tree {
constructor(tree: Tree);
readonly delegate: Tree;
}
/**
* Empty tree implementation for testing
*/
class EmptyTree implements Tree {
static readonly instance: EmptyTree;
}
/**
* Tree that filters files based on a predicate
*/
class FilterHostTree extends HostTree {
constructor(tree: HostTree, filter: FilePredicate<boolean>);
}
/**
* Tree scoped to a specific directory
*/
class ScopedTree extends DelegateTree {
constructor(tree: Tree, scope: string);
}
/**
* Null tree implementation (no operations)
*/
class NullTree implements Tree {
// Minimal implementation for testing
}/**
* Check if an object is a Tree instance
*/
function isTree(maybeTree: any): maybeTree is Tree;
/**
* Symbols used for tree identification
*/
const TreeSymbol: symbol;
const FileVisitorCancelToken: symbol;Usage Examples:
import { Tree, HostTree, ScopedTree, FilterHostTree } from "@angular-devkit/schematics";
// Create empty tree
const emptyTree = Tree.empty();
// Branch existing tree
const branch = Tree.branch(tree);
// Merge trees with strategy
Tree.merge(tree, otherTree, MergeStrategy.AllowOverwriteConflict);
// Partition tree based on file type
const [jsFiles, otherFiles] = Tree.partition(tree, (path) =>
path.endsWith('.js') || path.endsWith('.ts')
);Interfaces representing files and directories within the tree.
/**
* Represents a file within the tree
*/
interface FileEntry {
readonly path: Path;
readonly content: Buffer;
}
/**
* Represents a directory within the tree
*/
interface DirEntry {
readonly parent: DirEntry | null;
readonly path: Path;
readonly subdirs: PathFragment[];
readonly subfiles: PathFragment[];
/** Get subdirectory by name */
dir(name: PathFragment): DirEntry;
/** Get file by name, returns null if not found */
file(name: PathFragment): FileEntry | null;
/** Visit all files in directory tree */
visit(visitor: FileVisitor): void;
}Interface for making granular modifications to existing files.
/**
* Interface for recording file modifications with precise control
*/
interface UpdateRecorder {
/** Insert content to the left of the specified index */
insertLeft(index: number, content: Buffer | string): UpdateRecorder;
/** Insert content to the right of the specified index */
insertRight(index: number, content: Buffer | string): UpdateRecorder;
/** Remove content from specified index with given length */
remove(index: number, length: number): UpdateRecorder;
}Usage Examples:
import { Tree, UpdateRecorder } from "@angular-devkit/schematics";
function updateFile(tree: Tree): Tree {
const filePath = '/src/app/app.component.ts';
if (tree.exists(filePath)) {
const content = tree.readText(filePath);
const recorder = tree.beginUpdate(filePath);
// Find position to insert import
const importIndex = content.indexOf('import');
if (importIndex !== -1) {
recorder.insertLeft(importIndex, 'import { Injectable } from "@angular/core";\n');
}
// Find and replace specific text
const classIndex = content.indexOf('export class');
if (classIndex !== -1) {
recorder.insertLeft(classIndex, '@Injectable()\n');
}
// Commit the update
tree.commitUpdate(recorder);
}
return tree;
}Enumeration controlling how tree conflicts are resolved during merge operations.
/**
* Strategies for resolving conflicts during tree merge operations
*/
enum MergeStrategy {
/** Default strategy - throw error on conflicts */
Default = 0,
/** Always throw error on any conflict */
Error = 1,
/** Allow overwriting existing files */
AllowOverwriteConflict = 2,
/** Allow creating files that already exist */
AllowCreationConflict = 4,
/** Allow deleting files that don't exist */
AllowDeleteConflict = 8,
/** Allow content-only conflicts */
ContentOnly = AllowOverwriteConflict,
/** Allow all conflict types */
Overwrite = AllowOverwriteConflict | AllowCreationConflict | AllowDeleteConflict
}Concrete implementations of the Tree interface for different use cases.
/**
* Main tree implementation backed by virtual file system host
*/
class HostTree implements Tree {
constructor(host?: virtualFs.Host);
}
/**
* Tree implementation that delegates all operations to another tree
*/
class DelegateTree implements Tree {
constructor(other: Tree);
}
/**
* Tree that limits operations to a specific path scope
*/
class ScopedTree extends DelegateTree {
constructor(tree: Tree, root: Path);
}
/**
* Host tree that filters files based on predicate
*/
class FilterHostTree extends HostTree {
constructor(tree: Tree, filter: FilePredicate<boolean>);
}Utility functions for creating and manipulating trees.
/** Create an empty host tree */
function empty(): HostTree;
/** Create a branch (copy) of the given tree */
function branch(tree: Tree): Tree;
/** Merge two trees using the specified strategy */
function merge(tree: Tree, other: Tree, strategy?: MergeStrategy): Tree;
/** Partition tree into two based on predicate function */
function partition(tree: Tree, predicate: FilePredicate<boolean>): [Tree, Tree];type FilePredicate<T> = (path: Path, entry?: Readonly<FileEntry> | null) => T;
type FileVisitor = FilePredicate<void>;
type FileOperator = (entry: FileEntry) => FileEntry | null;
type Path = string & { __PRIVATE_DEVKIT_PATH: void };
type PathFragment = string & { __PRIVATE_DEVKIT_PATH_FRAGMENT: void };