
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
📁 A powerful, type-safe library for programmatically generating project structures with templates
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);
Build(structure, rootPath, variables)🛠️ 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' }
);
getFolder(folderName)📂 Retrieves the path of a folder by name.
import { getFolder } from 'forji';
const componentsPath = getFolder('components');
// -> '/path/to/project/src/components'
getFile(fileName)📄 Retrieves the path of a file by name.
import { getFile } from 'forji';
const readmePath = getFile('README.md');
// -> '/path/to/project/README.md'
getPath(name)🔍 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'
findFile(fileName, directory?)🔎 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 extensiongetFilesInFolder(folderName) - 📁 Returns all files in a specific folderProject 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!
MIT © 🏗️ [Hicham Jebara].
FAQs
Library for building project structures with dynamic folder/file names
We found that forji demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.