0
# Tree API
1
2
Virtual file system API for code generation and file system operations with change tracking. Provides a virtual abstraction over the file system that tracks changes and enables batch operations.
3
4
## Capabilities
5
6
### Tree Interface
7
8
Main virtual file system interface for reading, writing, and manipulating files.
9
10
```typescript { .api }
11
/**
12
* Virtual file system interface with change tracking
13
*/
14
export interface Tree {
15
/** Workspace root directory path */
16
root: string;
17
18
/** Read file as Buffer */
19
read(filePath: string): Buffer | null;
20
/** Read file as string with encoding */
21
read(filePath: string, encoding: BufferEncoding): string | null;
22
23
/** Write content to file */
24
write(filePath: string, content: Buffer | string, options?: TreeWriteOptions): void;
25
/** Check if file exists */
26
exists(filePath: string): boolean;
27
/** Delete file */
28
delete(filePath: string): void;
29
/** Rename/move file */
30
rename(from: string, to: string): void;
31
/** Check if path is a file (not directory) */
32
isFile(filePath: string): boolean;
33
/** List child files/directories in a directory */
34
children(dirPath: string): string[];
35
/** Get list of all tracked changes */
36
listChanges(): FileChange[];
37
/** Change file permissions */
38
changePermissions(filePath: string, mode: Mode): void;
39
}
40
41
/** File write options */
42
export interface TreeWriteOptions {
43
/** File permissions mode */
44
mode?: Mode;
45
}
46
47
/** File permission mode type */
48
export type Mode = number;
49
```
50
51
### Change Tracking
52
53
Types for tracking and applying file system changes.
54
55
```typescript { .api }
56
/**
57
* Describes a file system change operation
58
*/
59
export interface FileChange {
60
/** Path of the file being changed */
61
path: string;
62
/** Type of change operation */
63
type: 'CREATE' | 'DELETE' | 'UPDATE';
64
/** File content (null for deletions) */
65
content: Buffer | null;
66
/** Write options for the change */
67
options?: TreeWriteOptions;
68
}
69
```
70
71
### Tree Implementation
72
73
Concrete file system-backed implementation of the Tree interface.
74
75
```typescript { .api }
76
/**
77
* File system-backed implementation of Tree interface
78
*/
79
export class FsTree implements Tree {
80
constructor(root: string, isVerbose?: boolean);
81
82
root: string;
83
read(filePath: string): Buffer | null;
84
read(filePath: string, encoding: BufferEncoding): string | null;
85
write(filePath: string, content: Buffer | string, options?: TreeWriteOptions): void;
86
exists(filePath: string): boolean;
87
delete(filePath: string): void;
88
rename(from: string, to: string): void;
89
isFile(filePath: string): boolean;
90
children(dirPath: string): string[];
91
listChanges(): FileChange[];
92
changePermissions(filePath: string, mode: Mode): void;
93
}
94
```
95
96
### Change Application
97
98
Functions for applying tracked changes to the actual file system.
99
100
```typescript { .api }
101
/**
102
* Applies tracked changes to the file system
103
* @param root - Root directory to apply changes to
104
* @param fileChanges - Array of changes to apply
105
*/
106
export function flushChanges(root: string, fileChanges: FileChange[]): void;
107
108
/**
109
* Prints a summary of tracked changes
110
* @param fileChanges - Array of changes to print
111
* @param indent - Indentation string for formatting (defaults to empty string)
112
*/
113
export function printChanges(fileChanges: FileChange[], indent?: string): void;
114
```
115
116
## Usage Examples
117
118
### Basic File Operations
119
120
```typescript
121
import { FsTree } from "@nrwl/tao/shared/tree";
122
123
// Create a tree for the current workspace
124
const tree = new FsTree(process.cwd());
125
126
// Read files
127
const packageJson = tree.read('package.json', 'utf-8');
128
const binaryFile = tree.read('logo.png'); // Returns Buffer
129
130
// Check file existence
131
if (tree.exists('src/app.ts')) {
132
console.log('App file exists');
133
}
134
135
// Write files
136
tree.write('src/new-component.ts', `
137
export class NewComponent {
138
constructor() {
139
console.log('New component created');
140
}
141
}
142
`);
143
144
// Create directories by writing files with paths
145
tree.write('src/utils/helper.ts', 'export const helper = () => {};');
146
```
147
148
### Change Tracking and Application
149
150
```typescript
151
import { FsTree, flushChanges, printChanges } from "@nrwl/tao/shared/tree";
152
153
const tree = new FsTree('/path/to/workspace');
154
155
// Make several changes
156
tree.write('new-file.ts', 'export const newFunction = () => {};');
157
tree.write('src/existing.ts', 'updated content');
158
tree.delete('old-file.ts');
159
tree.rename('temp.ts', 'renamed.ts');
160
161
// Review changes before applying
162
const changes = tree.listChanges();
163
console.log(`Found ${changes.length} changes:`);
164
printChanges(changes, ' ');
165
166
// Apply all changes to the file system
167
flushChanges(tree.root, changes);
168
console.log('All changes applied to file system');
169
```
170
171
### File System Navigation
172
173
```typescript
174
import { FsTree } from "@nrwl/tao/shared/tree";
175
176
const tree = new FsTree('/workspace');
177
178
// List directory contents
179
const srcFiles = tree.children('src');
180
console.log('Source files:', srcFiles);
181
182
// Check file types
183
srcFiles.forEach(file => {
184
const fullPath = `src/${file}`;
185
if (tree.isFile(fullPath)) {
186
console.log(`${file} is a file`);
187
} else {
188
console.log(`${file} is a directory`);
189
}
190
});
191
192
// Recursively list all TypeScript files
193
function listTsFiles(tree: FsTree, dir: string = ''): string[] {
194
const tsFiles: string[] = [];
195
196
try {
197
const children = tree.children(dir);
198
199
for (const child of children) {
200
const childPath = dir ? `${dir}/${child}` : child;
201
202
if (tree.isFile(childPath) && child.endsWith('.ts')) {
203
tsFiles.push(childPath);
204
} else if (!tree.isFile(childPath)) {
205
tsFiles.push(...listTsFiles(tree, childPath));
206
}
207
}
208
} catch (error) {
209
// Directory might not exist or be accessible
210
}
211
212
return tsFiles;
213
}
214
215
const allTsFiles = listTsFiles(tree, 'src');
216
console.log('All TypeScript files:', allTsFiles);
217
```
218
219
### Code Generation Patterns
220
221
```typescript
222
import { FsTree, type TreeWriteOptions } from "@nrwl/tao/shared/tree";
223
224
function generateComponent(tree: FsTree, name: string, path: string) {
225
const componentDir = `${path}/${name}`;
226
227
// Generate component file
228
tree.write(`${componentDir}/${name}.component.ts`, `
229
import { Component } from '@angular/core';
230
231
@Component({
232
selector: 'app-${name}',
233
templateUrl: './${name}.component.html',
234
styleUrls: ['./${name}.component.css']
235
})
236
export class ${pascalCase(name)}Component {
237
constructor() {}
238
}
239
`);
240
241
// Generate template
242
tree.write(`${componentDir}/${name}.component.html`, `
243
<div class="${name}-container">
244
<h2>${name} works!</h2>
245
</div>
246
`);
247
248
// Generate styles
249
tree.write(`${componentDir}/${name}.component.css`, `
250
.${name}-container {
251
padding: 1rem;
252
}
253
`);
254
255
// Generate test file
256
tree.write(`${componentDir}/${name}.component.spec.ts`, `
257
import { ComponentFixture, TestBed } from '@angular/core/testing';
258
import { ${pascalCase(name)}Component } from './${name}.component';
259
260
describe('${pascalCase(name)}Component', () => {
261
let component: ${pascalCase(name)}Component;
262
let fixture: ComponentFixture<${pascalCase(name)}Component>;
263
264
beforeEach(() => {
265
TestBed.configureTestingModule({
266
declarations: [${pascalCase(name)}Component]
267
});
268
fixture = TestBed.createComponent(${pascalCase(name)}Component);
269
component = fixture.componentInstance;
270
});
271
272
it('should create', () => {
273
expect(component).toBeTruthy();
274
});
275
});
276
`);
277
}
278
279
function pascalCase(str: string): string {
280
return str.charAt(0).toUpperCase() + str.slice(1);
281
}
282
283
// Usage
284
const tree = new FsTree('/workspace');
285
generateComponent(tree, 'user-profile', 'src/app/components');
286
287
// Apply changes
288
const changes = tree.listChanges();
289
printChanges(changes);
290
flushChanges(tree.root, changes);
291
```
292
293
### File Permissions and Options
294
295
```typescript
296
import { FsTree, type TreeWriteOptions } from "@nrwl/tao/shared/tree";
297
298
const tree = new FsTree('/workspace');
299
300
// Write executable script
301
const scriptOptions: TreeWriteOptions = {
302
mode: 0o755 // rwxr-xr-x
303
};
304
305
tree.write('scripts/build.sh', `#!/bin/bash
306
echo "Building project..."
307
npm run build
308
`, scriptOptions);
309
310
// Or change permissions after writing
311
tree.write('scripts/deploy.sh', '#!/bin/bash\necho "Deploying..."');
312
tree.changePermissions('scripts/deploy.sh', 0o755);
313
314
// Apply changes
315
flushChanges(tree.root, tree.listChanges());
316
```
317
318
## Integration with Code Generation
319
320
The Tree API is commonly used with other @nrwl/tao APIs for comprehensive code generation:
321
322
```typescript
323
import { FsTree, flushChanges } from "@nrwl/tao/shared/tree";
324
import { Workspaces } from "@nrwl/tao/shared/workspace";
325
import { logger } from "@nrwl/tao/shared/logger";
326
327
async function generateLibrary(workspaceRoot: string, libName: string) {
328
const tree = new FsTree(workspaceRoot);
329
const workspaces = new Workspaces(workspaceRoot);
330
331
try {
332
// Read existing workspace configuration
333
const projects = workspaces.readProjectsConfigurations();
334
335
// Generate library files
336
const libPath = `libs/${libName}`;
337
tree.write(`${libPath}/src/index.ts`, `export * from './lib/${libName}';`);
338
tree.write(`${libPath}/src/lib/${libName}.ts`, `
339
export function ${libName}(): string {
340
return '${libName}';
341
}
342
`);
343
344
// Update workspace configuration
345
const updatedProjects = {
346
...projects,
347
projects: {
348
...projects.projects,
349
[libName]: {
350
root: libPath,
351
sourceRoot: `${libPath}/src`,
352
projectType: 'library' as const,
353
targets: {
354
build: {
355
executor: '@nx/js:tsc',
356
options: {
357
outputPath: `dist/${libPath}`,
358
main: `${libPath}/src/index.ts`,
359
tsConfig: `${libPath}/tsconfig.lib.json`
360
}
361
}
362
}
363
}
364
}
365
};
366
367
// Write updated configuration (this would typically be done through workspace APIs)
368
tree.write('workspace.json', JSON.stringify(updatedProjects, null, 2));
369
370
// Apply all changes
371
const changes = tree.listChanges();
372
logger.info(`Generating library '${libName}' with ${changes.length} file operations`);
373
374
flushChanges(tree.root, changes);
375
logger.info(`Library '${libName}' generated successfully`);
376
377
} catch (error) {
378
logger.error('Failed to generate library:', error);
379
throw error;
380
}
381
}
382
```