cvm-cli
Version:
A unified CLI tool for managing PHP, Node.js, and Python versions with virtual environment and dependency management support.
237 lines (198 loc) ⢠7.34 kB
JavaScript
const fs = require('fs-extra');
const path = require('path');
const chalk = require('chalk');
const inquirer = require('inquirer');
const { spawn } = require('child_process');
const CVMUtils = require('./utils');
const templates = require('./templates');
class ProjectManager {
static getAvailableTemplates() {
return Object.keys(templates).sort();
}
static getTemplate(templateName) {
return templates[templateName];
}
static getTemplatesByLanguage(language) {
return Object.keys(templates).filter(key =>
templates[key].language === language
);
}
static async listTemplates() {
console.log(chalk.blue('\nš Available Project Templates:\n'));
const templatesByLanguage = {};
Object.keys(templates).forEach(key => {
const template = templates[key];
if (!templatesByLanguage[template.language]) {
templatesByLanguage[template.language] = [];
}
templatesByLanguage[template.language].push({
name: key,
description: template.description
});
});
Object.keys(templatesByLanguage).sort().forEach(language => {
console.log(chalk.yellow(`\n${language.toUpperCase()}:`));
templatesByLanguage[language].forEach(template => {
console.log(` ${chalk.green(template.name.padEnd(15))} - ${template.description}`);
});
});
console.log(chalk.cyan('\nUsage: cpm make <template> <project-name>\n'));
}
static async createProject(templateName, projectName, options = {}) {
const template = this.getTemplate(templateName);
if (!template) {
console.log(chalk.red(`ā Template '${templateName}' not found.`));
console.log(chalk.yellow('Run "cpm list" to see available templates.'));
return false;
}
console.log(chalk.blue(`\nš Creating ${template.description} project: ${projectName}\n`));
try {
// Check if project directory already exists
const projectPath = path.resolve(projectName);
if (await fs.pathExists(projectPath)) {
console.log(chalk.red(`ā Directory '${projectName}' already exists.`));
return false;
}
// Check requirements
await this.checkRequirements(template);
// Run setup commands if needed
if (template.setup) {
console.log(chalk.yellow('š¦ Installing dependencies...'));
for (const setupCmd of template.setup) {
await this.executeCommand(setupCmd);
}
}
// Create the project
const fullCommand = `${template.command} ${projectName}`;
console.log(chalk.blue(`Executing: ${fullCommand}`));
await this.executeCommand(fullCommand);
// Run post-setup commands if needed
if (template.postSetup) {
console.log(chalk.yellow('āļø Running post-setup commands...'));
const oldCwd = process.cwd();
process.chdir(projectPath);
for (const postCmd of template.postSetup) {
await this.executeCommand(postCmd);
}
process.chdir(oldCwd);
}
console.log(chalk.green(`\nā
Successfully created ${template.description} project: ${projectName}`));
console.log(chalk.cyan(`\nNext steps:`));
console.log(chalk.white(` cd ${projectName}`));
// Add template-specific next steps
this.showNextSteps(template, projectName);
return true;
} catch (error) {
console.log(chalk.red(`ā Error creating project: ${error.message}`));
return false;
}
}
static async checkRequirements(template) {
console.log(chalk.yellow('š Checking requirements...'));
for (const requirement of template.requirements) {
try {
await this.executeCommand(`${requirement} --version`, { silent: true });
console.log(chalk.green(` ā ${requirement} is available`));
} catch (error) {
console.log(chalk.red(` ā ${requirement} is not installed or not in PATH`));
throw new Error(`Missing requirement: ${requirement}`);
}
}
}
static executeCommand(command, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn(command, [], {
shell: true,
stdio: options.silent ? 'pipe' : 'inherit',
...options
});
let stdout = '';
let stderr = '';
if (options.silent && child.stdout) {
child.stdout.on('data', (data) => {
stdout += data.toString();
});
}
if (options.silent && child.stderr) {
child.stderr.on('data', (data) => {
stderr += data.toString();
});
}
child.on('close', (code) => {
if (code === 0) {
resolve({ stdout, stderr, code });
} else {
reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
}
});
child.on('error', (error) => {
reject(error);
});
});
}
static showNextSteps(template, projectName) {
switch (template.language) {
case 'php':
if (template.command.includes('laravel')) {
console.log(chalk.white(` php artisan serve`));
} else if (template.command.includes('symfony')) {
console.log(chalk.white(` symfony server:start`));
}
break;
case 'python':
if (template.command.includes('django')) {
console.log(chalk.white(` python manage.py runserver`));
} else if (template.command.includes('flask')) {
console.log(chalk.white(` flask run`));
} else if (template.command.includes('fastapi')) {
console.log(chalk.white(` uvicorn main:app --reload`));
}
break;
case 'node':
console.log(chalk.white(` npm install`));
if (template.command.includes('next')) {
console.log(chalk.white(` npm run dev`));
} else if (template.command.includes('react')) {
console.log(chalk.white(` npm start`));
} else if (template.command.includes('vue')) {
console.log(chalk.white(` npm run dev`));
} else if (template.command.includes('angular')) {
console.log(chalk.white(` ng serve`));
} else {
console.log(chalk.white(` npm run dev`));
}
break;
}
}
static async interactiveCreate() {
console.log(chalk.blue('šÆ Interactive Project Creator\n'));
const answers = await inquirer.prompt([
{
type: 'list',
name: 'template',
message: 'Select a project template:',
choices: Object.keys(templates).map(key => ({
name: `${key} - ${templates[key].description}`,
value: key
})).sort((a, b) => a.name.localeCompare(b.name)),
pageSize: 15
},
{
type: 'input',
name: 'projectName',
message: 'Enter project name:',
validate: (input) => {
if (!input.trim()) {
return 'Project name is required';
}
if (!/^[a-zA-Z0-9-_]+$/.test(input)) {
return 'Project name can only contain letters, numbers, hyphens, and underscores';
}
return true;
}
}
]);
return this.createProject(answers.template, answers.projectName);
}
}
module.exports = ProjectManager;