fsBuilder Service
The fsBuilder
service provides utilities for generating files and directories during code generation and scaffolding operations. It's primarily used by command generators to create project structure, files, and boilerplate code with user confirmation and conflict resolution.
Interface
The service exposes three main methods:
generateDir(dirPath, fn)
- Creates a directory and executes a function within its contextgenerateFile(name, options, fn)
- Generates a file with template contentinProjectRootDir(fn)
- Executes a function in the project root directory context
Methods
generateDir(dirPath, fn = () => {})
Creates a directory structure and temporarily changes the working directory to execute the provided function.
Parameters:
dirPath
(string) - Absolute or relative path to createfn
(function) - Optional function to execute within the directory context
Behavior:
- Converts relative paths to absolute paths using
process.cwd()
- Creates parent directories recursively if they don't exist
- Changes working directory to the created directory
- Executes the provided function
- Restores the previous working directory
generateFile(name, options, fn)
Generates a file with content produced by a template function, handling conflicts and user confirmation.
Parameters:
name
(string) - File path/name (can include subdirectories)options
(object) - Optional configuration:skipIfExists
(boolean) - Skip generation if file already existsforce
(boolean) - Force overwrite without confirmation
fn
(function) - Template function that receives rendering helpers
Behavior:
- Creates parent directories automatically if the name includes subdirectories
- Skips generation if file exists and content is identical
- Prompts for confirmation before overwriting existing files (unless
force
orskipIfExists
is set) - Uses the text rendering system with
line()
,indent()
, andecho()
helpers
inProjectRootDir(fn)
Executes a function in the context of the project root directory.
Parameters:
fn
(function) - Function to execute in project root context
Behavior:
- Temporarily changes to the project root directory
- Executes the provided function
- Restores the previous working directory
Template Functions
When using generateFile()
, the template function receives an object with rendering helpers:
line(content = '')
- Adds a line of content (with newline)indent(fn)
- Indents content generated by the nested function (4 spaces)echo(content)
- Adds content without automatic newlines
Usage Examples
Basic File Generation
const { generateFile } = this.fsBuilder;
await generateFile('config.js', ({ line, indent }) => {
line('export default {');
indent(({ line }) => {
line("name: 'MyProject',");
line("version: '1.0.0'");
});
line('};');
});
Generating Files with Directory Structure
const { generateFile } = this.fsBuilder;
await generateFile('lib/services/my_service.js', ({ line, indent }) => {
line('export default {');
indent(({ line, indent }) => {
line('create(){');
indent(({ line }) => {
line("return 'My Service Implementation';");
});
line('}');
});
line('};');
});
Working in Project Root
const { inProjectRootDir, generateFile } = this.fsBuilder;
await inProjectRootDir(async () => {
await generateFile('package.json', ({ echo }) => {
echo(JSON.stringify({
name: 'my-project',
version: '1.0.0'
}, null, 2));
});
});
Creating Directory Structure
const { generateDir, generateFile } = this.fsBuilder;
await generateDir('my-project', async () => {
await generateFile('README.md', ({ line }) => {
line('# My Project');
line();
line('Description of my project.');
});
await generateFile('lib/index.js', ({ line }) => {
line("export default 'Hello World';");
});
});
Handling File Conflicts
const { generateFile } = this.fsBuilder;
// Skip if file already exists
await generateFile('config.js', { skipIfExists: true }, ({ line }) => {
line('// Default config');
});
// Force overwrite without confirmation
await generateFile('temp.js', { force: true }, ({ line }) => {
line('// Temporary file');
});
Complex Template with JSON Output
const { generateFile } = this.fsBuilder;
await generateFile('pinstripe.config.js', ({ line, indent }) => {
line();
line('const environment = process.env.NODE_ENV || "development";');
line();
line('let database;');
line('if(environment == "production"){');
indent(({ line, indent }) => {
line('database = {');
indent(({ line }) => {
line('adapter: "mysql",');
line('host: "localhost",');
line('user: "root",');
line('password: "",');
line('database: `myapp_${environment}`');
});
line('};');
});
line('} else {');
indent(({ line, indent }) => {
line('database = {');
indent(({ line }) => {
line('adapter: "sqlite",');
line('filename: `${environment}.db`');
});
line('};');
});
line('}');
line();
line('export default {');
indent(({ line }) => {
line('database');
});
line('};');
});
Service Generation Pattern
export default {
async run(){
const { name = '' } = this.params;
const { inProjectRootDir, generateFile } = this.fsBuilder;
await inProjectRootDir(async () => {
// Create file importer if it doesn't exist
await generateFile(`lib/services/_file_importer.js`, { skipIfExists: true }, ({ line }) => {
line();
line(`export { ServiceFactory as default } from 'pinstripe';`);
line();
});
// Generate the actual service file
await generateFile(`lib/services/${this.inflector.snakeify(name)}.js`, ({ line, indent }) => {
line(`export default {`);
indent(({ line, indent }) => {
line('create(){');
indent(({ line }) => {
line(`return 'Example ${this.inflector.camelize(name)} service'`);
});
line('}');
});
line('};');
});
});
}
};
User Interaction
The service includes a confirmation system for file conflicts:
- Y (or Enter) - Proceed with the operation
- N - Skip the current file
- A - Abort the entire process (exits the program)
The confirmation prompt appears as: Are you sure you want to update /path/to/file.js? Y/n/a:
Common Patterns
-
File Importer Pattern: Many generators create a
_file_importer.js
withskipIfExists: true
to ensure it exists without overwriting custom implementations. -
Directory + File Generation: Creating a directory and then generating files within it is a common pattern for project scaffolding.
-
Project Root Context: Most file generation happens within
inProjectRootDir()
to ensure files are created in the correct project location. -
Template Inheritance: Using
line()
andindent()
helpers to create properly formatted code with consistent indentation.