create-exam-project
Version:
Create exam projects with React + Express + PostgreSQL in seconds
274 lines (228 loc) • 9.97 kB
JavaScript
const path = require('path');
const fs = require('fs-extra');
const { program } = require('commander');
const inquirer = require('inquirer');
const chalk = require('chalk');
const ora = require('ora');
const { execSync } = require('child_process');
const validateProjectName = require('validate-npm-package-name');
const packageJson = require('../package.json');
// Варианты проектов
const variants = {
variant1: {
name: 'Буквоежка',
description: 'Система обмена книгами',
admin: { login: 'admin', password: 'bookworm' }
},
variant2: {
name: 'Грузовозофф',
description: 'Система грузоперевозок',
admin: { login: 'admin', password: 'gruzovik2024' }
},
variant3: {
name: 'Корочки.есть',
description: 'Система онлайн курсов',
admin: { login: 'admin', password: 'education' }
},
variant4: {
name: 'Я буду кушац',
description: 'Система бронирования столиков',
admin: { login: 'admin', password: 'restaurant' }
},
universal: {
name: 'Универсальный',
description: 'Базовый шаблон',
admin: { login: 'admin', password: 'admin123' }
}
};
console.log(chalk.blue(`
╔═══════════════════════════════════════╗
║ CREATE EXAM PROJECT v${packageJson.version} ║
║ Быстрый старт для экзамена ║
╚═══════════════════════════════════════╝
`));
// Если запущено без аргументов, показываем интерактивное меню
if (process.argv.length === 2) {
console.log(chalk.yellow('💡 Совет: для быстрого создания используйте:'));
console.log(chalk.gray(' npx create-exam-project my-project --variant variant1 --yes\n'));
}
program
.name('create-exam-project')
.version(packageJson.version)
.argument('[project-name]', 'имя проекта')
.option('-v, --variant <type>', 'вариант проекта')
.option('-y, --yes', 'использовать настройки по умолчанию')
.option('--skip-install', 'пропустить установку зависимостей')
.option('--skip-git', 'не инициализировать git')
.helpOption('-h, --help', 'показать помощь')
.parse();
async function init() {
const options = program.opts();
let projectName = program.args[0];
let variant = options.variant;
// Интерактивный режим
if (!options.yes && (!projectName || !variant)) {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'projectName',
message: 'Название проекта:',
default: 'my-exam-app',
when: !projectName,
validate: (input) => {
const validation = validateProjectName(input);
return validation.validForNewPackages || 'Некорректное название проекта';
}
},
{
type: 'list',
name: 'variant',
message: 'Выберите вариант:',
when: !variant,
choices: [
new inquirer.Separator('═══ Варианты заданий ═══'),
{
name: chalk.green('📚 Вариант 1: Буквоежка') + chalk.gray(' (обмен книгами)'),
value: 'variant1',
short: 'Буквоежка'
},
{
name: chalk.yellow('🚚 Вариант 2: Грузовозофф') + chalk.gray(' (грузоперевозки)'),
value: 'variant2',
short: 'Грузовозофф'
},
{
name: chalk.blue('🎓 Вариант 3: Корочки.есть') + chalk.gray(' (онлайн курсы)'),
value: 'variant3',
short: 'Корочки.есть'
},
{
name: chalk.magenta('🍽️ Вариант 4: Я буду кушац') + chalk.gray(' (бронирование)'),
value: 'variant4',
short: 'Я буду кушац'
},
new inquirer.Separator('═════════════════════════'),
{
name: chalk.cyan('🚀 Универсальный шаблон'),
value: 'universal',
short: 'Универсальный'
}
],
default: 0
}
]);
projectName = projectName || answers.projectName;
variant = variant || answers.variant;
// Показываем информацию о выбранном варианте
if (answers.variant) {
const selectedVariant = variants[answers.variant];
console.log('');
console.log(chalk.green('✅ Выбран: ') + chalk.yellow(selectedVariant.name));
console.log(chalk.gray('🔐 Админ: ') + chalk.cyan(`${selectedVariant.admin.login} / ${selectedVariant.admin.password}`));
console.log('');
}
}
// Значения по умолчанию
projectName = projectName || 'my-exam-app';
variant = variant || 'universal';
const projectPath = path.join(process.cwd(), projectName);
// Проверка существования папки
if (fs.existsSync(projectPath)) {
console.error(chalk.red(`\n❌ Папка ${projectName} уже существует!`));
process.exit(1);
}
const spinner = ora('Создание проекта...').start();
try {
// Создаем папку проекта
fs.ensureDirSync(projectPath);
// Копируем файлы
const templatePath = path.join(__dirname, '..', 'templates', 'base');
// Копируем основные файлы
spinner.text = 'Копирование файлов...';
// Структура проекта
const templateBase = path.join(__dirname, '..', 'templates', 'base');
// Копируем все из templates/base
if (fs.existsSync(templateBase)) {
fs.copySync(templateBase, projectPath);
} else {
throw new Error('Шаблоны не найдены');
}
// Настройка варианта
spinner.text = 'Настройка варианта...';
const variantConfig = variants[variant];
// Создаем .env файл
const envContent = `# Database
DB_HOST=localhost
DB_PORT=5432
DB_NAME=exam_db
DB_USER=postgres
DB_PASSWORD=your_password
# Server
PORT=5000
# JWT
JWT_SECRET=your_secret_key_${Date.now()}
# Admin
ADMIN_LOGIN=${variantConfig.admin.login}
ADMIN_PASSWORD=${variantConfig.admin.password}
`;
fs.writeFileSync(path.join(projectPath, 'server', '.env'), envContent);
fs.writeFileSync(path.join(projectPath, 'server', '.env.example'), envContent.replace('your_password', 'your_password_here'));
// Обновляем конфигурацию клиента
const clientConfigPath = path.join(projectPath, 'client', 'src', 'contexts', 'ConfigContext.jsx');
if (fs.existsSync(clientConfigPath)) {
let configContent = fs.readFileSync(clientConfigPath, 'utf8');
configContent = configContent.replace(
"localStorage.getItem('currentVariant') || 'universal'",
`localStorage.getItem('currentVariant') || '${variant}'`
);
fs.writeFileSync(clientConfigPath, configContent);
}
// Git init
if (!options.skipGit) {
spinner.text = 'Инициализация git...';
try {
execSync('git init', { cwd: projectPath, stdio: 'ignore' });
} catch (e) {
// Игнорируем ошибки git
}
}
// Установка зависимостей
if (!options.skipInstall) {
spinner.text = 'Установка зависимостей (это может занять несколько минут)...';
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
// Устанавливаем корневые зависимости
execSync(`${npmCommand} install`, { cwd: projectPath, stdio: 'ignore' });
// Устанавливаем серверные зависимости
spinner.text = 'Установка серверных зависимостей...';
execSync(`${npmCommand} install`, { cwd: path.join(projectPath, 'server'), stdio: 'ignore' });
// Устанавливаем клиентские зависимости
spinner.text = 'Установка клиентских зависимостей...';
execSync(`${npmCommand} install`, { cwd: path.join(projectPath, 'client'), stdio: 'ignore' });
}
spinner.succeed('Проект создан успешно!');
console.log(`
${chalk.green('✅ Готово!')}
${chalk.blue('Следующие шаги:')}
${chalk.yellow(`cd ${projectName}`)}
${chalk.gray('# Настройте PostgreSQL в server/.env')}
${chalk.yellow('npm run dev')}
${chalk.cyan('Данные администратора:')}
Логин: ${chalk.green(variantConfig.admin.login)}
Пароль: ${chalk.green(variantConfig.admin.password)}
${chalk.gray('Frontend: http://localhost:3000')}
${chalk.gray('Backend: http://localhost:5000')}
${chalk.magenta('Удачи на экзамене! 🎉')}
`);
} catch (error) {
spinner.fail('Ошибка при создании проекта');
console.error(chalk.red(error.message));
// Удаляем папку при ошибке
if (fs.existsSync(projectPath)) {
fs.removeSync(projectPath);
}
process.exit(1);
}
}
// Запускаем
init().catch(console.error);