initrajs
Version:
⚡ InitraJS - JavaScript CLI Toolkit | Lightning-fast scaffolding for React, Next.js, Node.js with TypeScript | The future of JavaScript development | 10x faster than create-react-app | Ultimate developer productivity tool
403 lines (355 loc) • 14.2 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import chalk from 'chalk';
const validateName = (name) => /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name);
const detectFramework = () => {
const cwd = process.cwd();
const packagePath = path.join(cwd, 'package.json');
if (fs.existsSync(packagePath)) {
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8'));
if (pkg.dependencies?.next || pkg.devDependencies?.next)
return 'next';
if (pkg.dependencies?.react || pkg.devDependencies?.react)
return 'react';
if (pkg.dependencies?.express || pkg.devDependencies?.express)
return 'node';
}
return 'react';
};
const getClientServiceTemplate = (serviceName, isTypeScript, framework) => {
const capitalizedName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1);
const typeDefinitions = isTypeScript ? `
// Types
interface ${capitalizedName}Data {
id: string;
[key: string]: any;
}
interface ${capitalizedName}CreateData {
[key: string]: any;
}
interface ${capitalizedName}UpdateData {
[key: string]: any;
}
interface ApiResponse<T> {
data: T;
message: string;
success: boolean;
}
` : '';
const baseURL = framework === 'next' ? '/api' : 'http://localhost:3001/api';
return `${typeDefinitions}
class ${capitalizedName}Service {
private baseURL = '${baseURL}';
// GET - Fetch all ${serviceName}s
async getAll()${isTypeScript ? `: Promise<ApiResponse<${capitalizedName}Data[]>>` : ''} {
try {
const response = await fetch(\`\${this.baseURL}/${serviceName}\`);
if (!response.ok) {
throw new Error(\`HTTP error! status: \${response.status}\`);
}
return await response.json();
} catch (error) {
console.error('Error fetching ${serviceName}s:', error);
throw error;
}
}
// GET - Fetch single ${serviceName} by ID
async getById(id${isTypeScript ? ': string' : ''})${isTypeScript ? `: Promise<ApiResponse<${capitalizedName}Data>>` : ''} {
try {
const response = await fetch(\`\${this.baseURL}/${serviceName}/\${id}\`);
if (!response.ok) {
throw new Error(\`HTTP error! status: \${response.status}\`);
}
return await response.json();
} catch (error) {
console.error(\`Error fetching ${serviceName} with id \${id}:\`, error);
throw error;
}
}
// POST - Create new ${serviceName}
async create(data${isTypeScript ? `: ${capitalizedName}CreateData` : ''})${isTypeScript ? `: Promise<ApiResponse<${capitalizedName}Data>>` : ''} {
try {
const response = await fetch(\`\${this.baseURL}/${serviceName}\`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(\`HTTP error! status: \${response.status}\`);
}
return await response.json();
} catch (error) {
console.error('Error creating ${serviceName}:', error);
throw error;
}
}
// PUT - Update ${serviceName}
async update(id${isTypeScript ? ': string' : ''}, data${isTypeScript ? `: ${capitalizedName}UpdateData` : ''})${isTypeScript ? `: Promise<ApiResponse<${capitalizedName}Data>>` : ''} {
try {
const response = await fetch(\`\${this.baseURL}/${serviceName}/\${id}\`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(\`HTTP error! status: \${response.status}\`);
}
return await response.json();
} catch (error) {
console.error(\`Error updating ${serviceName} with id \${id}:\`, error);
throw error;
}
}
// DELETE - Delete ${serviceName}
async delete(id${isTypeScript ? ': string' : ''})${isTypeScript ? `: Promise<ApiResponse<{ id: string }>>` : ''} {
try {
const response = await fetch(\`\${this.baseURL}/${serviceName}/\${id}\`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(\`HTTP error! status: \${response.status}\`);
}
return await response.json();
} catch (error) {
console.error(\`Error deleting ${serviceName} with id \${id}:\`, error);
throw error;
}
}
}
// Export singleton instance
export const ${serviceName}Service = new ${capitalizedName}Service();
export default ${serviceName}Service;`;
};
const getServerServiceTemplate = (serviceName, isTypeScript, framework) => {
const capitalizedName = serviceName.charAt(0).toUpperCase() + serviceName.slice(1);
const typeDefinitions = isTypeScript ? `
// Types
interface ${capitalizedName}Data {
id: string;
createdAt: Date;
updatedAt: Date;
[key: string]: any;
}
interface ${capitalizedName}CreateData {
[key: string]: any;
}
interface ${capitalizedName}UpdateData {
[key: string]: any;
}
` : '';
if (framework === 'node') {
return `${typeDefinitions}
import { v4 as uuidv4 } from 'uuid';
class ${capitalizedName}Service {
private data${isTypeScript ? `: ${capitalizedName}Data[]` : ''} = [];
// Get all ${serviceName}s
async findAll()${isTypeScript ? `: Promise<${capitalizedName}Data[]>` : ''} {
try {
// Replace with actual database query
return this.data;
} catch (error) {
console.error('Error fetching ${serviceName}s:', error);
throw new Error('Failed to fetch ${serviceName}s');
}
}
// Get ${serviceName} by ID
async findById(id${isTypeScript ? ': string' : ''})${isTypeScript ? `: Promise<${capitalizedName}Data | null>` : ''} {
try {
// Replace with actual database query
const item = this.data.find(item => item.id === id);
return item || null;
} catch (error) {
console.error(\`Error fetching ${serviceName} with id \${id}:\`, error);
throw new Error(\`Failed to fetch ${serviceName}\`);
}
}
// Create new ${serviceName}
async create(data${isTypeScript ? `: ${capitalizedName}CreateData` : ''})${isTypeScript ? `: Promise<${capitalizedName}Data>` : ''} {
try {
const newItem${isTypeScript ? `: ${capitalizedName}Data` : ''} = {
id: uuidv4(),
...data,
createdAt: new Date(),
updatedAt: new Date(),
};
// Replace with actual database insert
this.data.push(newItem);
return newItem;
} catch (error) {
console.error('Error creating ${serviceName}:', error);
throw new Error('Failed to create ${serviceName}');
}
}
// Update ${serviceName}
async update(id${isTypeScript ? ': string' : ''}, data${isTypeScript ? `: ${capitalizedName}UpdateData` : ''})${isTypeScript ? `: Promise<${capitalizedName}Data | null>` : ''} {
try {
// Replace with actual database update
const index = this.data.findIndex(item => item.id === id);
if (index === -1) {
return null;
}
this.data[index] = {
...this.data[index],
...data,
updatedAt: new Date(),
};
return this.data[index];
} catch (error) {
console.error(\`Error updating ${serviceName} with id \${id}:\`, error);
throw new Error('Failed to update ${serviceName}');
}
}
// Delete ${serviceName}
async delete(id${isTypeScript ? ': string' : ''})${isTypeScript ? `: Promise<boolean>` : ''} {
try {
// Replace with actual database delete
const index = this.data.findIndex(item => item.id === id);
if (index === -1) {
return false;
}
this.data.splice(index, 1);
return true;
} catch (error) {
console.error(\`Error deleting ${serviceName} with id \${id}:\`, error);
throw new Error('Failed to delete ${serviceName}');
}
}
}
// Export singleton instance
export const ${serviceName}Service = new ${capitalizedName}Service();
export default ${serviceName}Service;`;
}
else {
// Next.js server service
return `${typeDefinitions}
class ${capitalizedName}Service {
private baseURL = process.env.DATABASE_URL || 'your-database-connection';
// Get all ${serviceName}s
async findAll()${isTypeScript ? `: Promise<${capitalizedName}Data[]>` : ''} {
try {
// Replace with actual database query (Prisma, MongoDB, etc.)
// Example with Prisma: return await prisma.${serviceName}.findMany();
// Mock data for now
return [];
} catch (error) {
console.error('Error fetching ${serviceName}s:', error);
throw new Error('Failed to fetch ${serviceName}s');
}
}
// Get ${serviceName} by ID
async findById(id${isTypeScript ? ': string' : ''})${isTypeScript ? `: Promise<${capitalizedName}Data | null>` : ''} {
try {
// Replace with actual database query
// Example with Prisma: return await prisma.${serviceName}.findUnique({ where: { id } });
// Mock implementation
return null;
} catch (error) {
console.error(\`Error fetching ${serviceName} with id \${id}:\`, error);
throw new Error(\`Failed to fetch ${serviceName}\`);
}
}
// Create new ${serviceName}
async create(data${isTypeScript ? `: ${capitalizedName}CreateData` : ''})${isTypeScript ? `: Promise<${capitalizedName}Data>` : ''} {
try {
// Replace with actual database insert
// Example with Prisma: return await prisma.${serviceName}.create({ data });
// Mock implementation
const newItem${isTypeScript ? `: ${capitalizedName}Data` : ''} = {
id: Math.random().toString(36).substr(2, 9),
...data,
createdAt: new Date(),
updatedAt: new Date(),
};
return newItem;
} catch (error) {
console.error('Error creating ${serviceName}:', error);
throw new Error('Failed to create ${serviceName}');
}
}
// Update ${serviceName}
async update(id${isTypeScript ? ': string' : ''}, data${isTypeScript ? `: ${capitalizedName}UpdateData` : ''})${isTypeScript ? `: Promise<${capitalizedName}Data | null>` : ''} {
try {
// Replace with actual database update
// Example with Prisma: return await prisma.${serviceName}.update({ where: { id }, data });
// Mock implementation
return null;
} catch (error) {
console.error(\`Error updating ${serviceName} with id \${id}:\`, error);
throw new Error('Failed to update ${serviceName}');
}
}
// Delete ${serviceName}
async delete(id${isTypeScript ? ': string' : ''})${isTypeScript ? `: Promise<boolean>` : ''} {
try {
// Replace with actual database delete
// Example with Prisma: await prisma.${serviceName}.delete({ where: { id } });
// Mock implementation
return true;
} catch (error) {
console.error(\`Error deleting ${serviceName} with id \${id}:\`, error);
throw new Error('Failed to delete ${serviceName}');
}
}
}
// Export singleton instance
export const ${serviceName}Service = new ${capitalizedName}Service();
export default ${serviceName}Service;`;
}
};
export const generateService = async (name, options) => {
try {
// Validate service name
if (!validateName(name)) {
console.error(chalk.red('❌ Invalid service name. Use only letters, numbers, hyphens and underscores. Must start with a letter.'));
return;
}
// Determine language preference
const isTypeScript = options.typescript || (!options.javascript && true);
const isClient = options.client || (!options.server && true); // Default to client
// Detect framework
const framework = detectFramework();
// Determine service directory
const serviceType = isClient ? 'client' : 'server';
const basePath = options.path || (isClient ? 'services' : 'lib');
const serviceDir = path.join(process.cwd(), 'src', basePath);
// Create directory
await fs.ensureDir(serviceDir);
// Generate service content
let serviceContent;
if (isClient) {
serviceContent = getClientServiceTemplate(name, isTypeScript, framework);
}
else {
serviceContent = getServerServiceTemplate(name, isTypeScript, framework);
}
// Determine file extension
const fileExt = isTypeScript ? 'ts' : 'js';
// Write service file
const serviceFile = path.join(serviceDir, `${name}.service.${fileExt}`);
await fs.writeFile(serviceFile, serviceContent);
// Success message
console.log(chalk.green(`✅ ${framework} ${serviceType} service created successfully!`));
console.log(chalk.blue(`📁 Location: ${path.relative(process.cwd(), serviceDir)}`));
console.log(chalk.blue(`📄 File created: ${name}.service.${fileExt}`));
if (isClient) {
console.log(chalk.yellow(`🌐 Client service with HTTP methods: GET, POST, PUT, DELETE`));
console.log(chalk.yellow(`📖 Usage: import { ${name}Service } from './${basePath}/${name}.service';`));
}
else {
console.log(chalk.yellow(`🗄️ Server service with data operations: findAll, findById, create, update, delete`));
console.log(chalk.yellow(`📖 Usage: import { ${name}Service } from './${basePath}/${name}.service';`));
console.log(chalk.magenta(`💡 Remember to replace mock implementations with actual database operations`));
}
}
catch (error) {
console.error(chalk.red('❌ Service generation failed'));
if (error instanceof Error) {
console.error(chalk.red(error.message));
}
process.exit(1);
}
};