📁 A powerful, type-safe library for programmatically generating project structures with templates
- 🎯 Type-Safe - Written in TypeScript with complete type definitions
- 🧩 Flexible - Create any folder/file structure with a simple object definition
- 🔄 Template Variables - Use variables in file names and content
- 🔍 File Discovery - Helpers to find files and folders in your generated structure
- 🎮 Singleton Pattern - Easy to use across your application without managing state
- 📂 Path Management - Access paths to your created files and folders
- ⚡ Asynchronous - Built with promises for non-blocking performance
npm install forji
# or
yarn add forji
# or
pnpm add forji
# or
bun add forji
import { Build } from 'forji';
async function main() {
// Define your project structure
const structure = {
src: {
components: {
Button$tsx: 'export const Button = () => <button>Click me</button>;',
index$ts: 'export * from "./Button";'
},
utils: {
helpers$ts: 'export const sum = (a: number, b: number) => a + b;'
},
index$ts: 'export * from "./components";'
},
package$json: JSON.stringify({
name: "{{projectName}}",
version: "1.0.0"
}, null, 2)
};
// Build the project with template variables
await Build(structure, './my-project', {
projectName: 'awesome-app'
});
console.log('🎉 Project successfully generated!');
}
main().catch(console.error);
🛠️ Creates a project structure based on a configuration object.
import { Build } from 'forji';
const result = await Build(
{
'src': { /* folders and files */ },
'README.md': '# My Project'
},
'./output',
{ version: '1.0.0' }
);
📂 Retrieves the path of a folder by name.
import { getFolder } from 'forji';
const componentsPath = getFolder('components');
// -> '/path/to/project/src/components'
📄 Retrieves the path of a file by name.
import { getFile } from 'forji';
const readmePath = getFile('README.md');
// -> '/path/to/project/README.md'
🔍 Retrieves the path of a file or folder by name.
import { getPath } from 'forji';
const path = getPath('Button.tsx');
// -> '/path/to/project/src/components/Button.tsx'
🔎 Recursively searches for a file starting from a directory.
import { findFile } from 'forji';
const configPath = await findFile('config.json');
// -> '/path/to/project/config.json' (if found)
The ProjectBuilder
class uses the singleton pattern and manages the internal state of your project.
import { ProjectBuilder } from 'forji';
const builder = ProjectBuilder.getInstance();
const tsFiles = builder.getFilesByExtension('ts');
-
getFilesByExtension(extension)
- 🏷️ Returns all files with the specified extension -
getFilesInFolder(folderName)
- 📁 Returns all files in a specific folder
Project Builder supports powerful templating with variables that can be used in both file/folder names and content using the {{variableName}}
syntax.
Variables are defined in the third parameter of Build()
and then referenced in your structure definition:
const structure = {
'src': {
'{{componentName}}$tsx': 'export const {{componentName}} = () => <div>Hello</div>;'
}
};
await Build(structure, './output', {
componentName: 'Header'
});
// Creates: ./output/src/Header.tsx with content: export const Header = () => <div>Hello</div>;
The TemplateVariables
type supports various primitive values:
type TemplateVariables = Record<string, string | number | boolean>;
// Example
await Build(structure, './output', {
appName: 'MyAwesomeApp', // string
version: 1.0, // number
isProduction: true, // boolean
port: 3000 // number
});
Variables can be used in any part of a path:
const structure = {
'{{srcFolder}}': {
'{{componentFolder}}': {
'{{prefix}}Button$tsx': '// {{copyright}}'
}
}
};
await Build(structure, './output', {
srcFolder: 'src',
componentFolder: 'components',
prefix: 'UI',
copyright: '© 2025 Example Corp'
});
// Creates: ./output/src/components/UIButton.tsx
Variables shine when generating dynamic file content:
const structure = {
'package$json': JSON.stringify({
name: "{{projectName}}",
version: "{{version}}",
description: "{{description}}",
scripts: {
"start": "node dist/index.js",
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc",
"test": "{{testCommand}}"
},
dependencies: {{dependencies}}
}, null, 2)
};
await Build(structure, './output', {
projectName: 'api-service',
version: '0.1.0',
description: 'Backend API service',
testCommand: 'jest --coverage',
dependencies: JSON.stringify({
"express": "^4.18.2",
"dotenv": "^16.0.3"
})
});
Generate configuration files with environment-specific settings:
function generateConfig(variables) {
const env = variables.environment;
let dbConfig = {};
if (env === 'development') {
dbConfig = {
host: 'localhost',
port: 5432
};
} else if (env === 'production') {
dbConfig = {
host: 'db.example.com',
port: 5432,
ssl: true
};
}
return JSON.stringify({
appName: variables.appName,
port: variables.port,
database: dbConfig,
logLevel: env === 'development' ? 'debug' : 'info'
}, null, 2);
}
const structure = {
'config$json': generateConfig
};
await Build(structure, './my-app', {
appName: 'MyService',
environment: 'development',
port: 3000
});
Variables can be mixed with dynamic content generation:
const structure = {
src: {
models: {
'{{entityName}}$ts': () => {
// This function has access to the variables via closure
return `export interface {{entityName}} {
id: string;
name: string;
createdAt: Date;
{{customFields}}
}`;
}
}
}
};
await Build(structure, './api', {
entityName: 'User',
customFields: `
email: string;
password: string;
role: 'admin' | 'user';`
});
For complex structures, consider pre-processing with variables:
function generateRouteFiles(routes) {
const files = {};
routes.forEach(route => {
const fileName = `${route.name}Route$ts`;
files[fileName] = `import { Router } from 'express';
const router = Router();
router.get('/${route.path}', (req, res) => {
res.json({ message: '${route.description}' });
});
export default router;`;
});
return files;
}
const routes = [
{ name: 'user', path: 'users', description: 'Get all users' },
{ name: 'product', path: 'products', description: 'Get all products' }
];
const structure = {
'src': {
'routes': generateRouteFiles(routes)
}
};
await Build(structure, './api');
The library supports special formatting for file names:
const structure = {
// Using $ for extensions
'tsconfig$json': '{ "compilerOptions": {} }',
// Using quotes for names with special characters
'"README.md"': '# Project Documentation'
};
import { Build, getFilesByExtension } from 'forji';
// Build project
const builder = await Build(structure, './output');
// Get all TypeScript files
const tsFiles = builder.getFilesByExtension('ts');
// Get all files in the components folder
const componentFiles = builder.getFilesInFolder('components');
const reactProject = {
src: {
components: {
App$tsx: `import React from 'react';
export const App = () => <div>Hello World</div>;`,
index$ts: 'export * from "./App";'
},
index$tsx: `import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './components';
ReactDOM.render(<App />, document.getElementById('root'));`
},
public: {
'index.html': `<!DOCTYPE html>
<html>
<head>
<title>{{projectName}}</title>
</head>
<body>
<div id="root"></div>
</body>
</html>`
},
package$json: JSON.stringify({
name: "{{projectName}}",
version: "{{version}}",
dependencies: {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}, null, 2)
};
forji/
├── src/
│ ├── index.ts # Main entry point
│ ├── ProjectBuilder.ts # Core builder class
│ ├── types.ts # Type definitions
│ └── utils.ts # Utility functions
├── package.json
└── README.md
Contributions, issues and feature requests are welcome!
- 🍴 Fork the project
- 🔨 Make your changes
- 🔄 Create a pull request
MIT © 🏗️ [Hicham Jebara].