UNPKG

create-tin

Version:

Scaffold a TypeScript or JavaScript Express boilerplate

281 lines (280 loc) 11 kB
#!/usr/bin/env node import { Command } from 'commander'; import inquirer from 'inquirer'; import fs from 'fs-extra'; import { fileURLToPath } from 'url'; import path from 'path'; import { execSync, spawn } from 'child_process'; import chalk from 'chalk'; import ora from 'ora'; const program = new Command(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); program .name('create-tin') .description('Scaffold a TypeScript or JavaScript Express boilerplate') .option('--ts', 'Use TypeScript') .option('--js', 'Use JavaScript') .option('--skip-git', 'Skip Git repository initialization') .option('--git', 'Initialize Git repository') .argument('[project-name]', 'Name of the project') .parse(); const options = program.opts(); const projectName = program.args[0] || 'my-express-app'; async function chooseTemplate() { const answers = {}; // Language selection if (options.ts) { answers.language = 'ts'; } else if (options.js) { answers.language = 'js'; } else { const { language } = await inquirer.prompt([ { type: 'list', name: 'language', message: chalk.bold('Choose a language:'), choices: [ { name: `${chalk.blue('●')} ${chalk.blue.bold('TypeScript')} ${chalk.gray('- Type-safe JavaScript')}`, value: 'ts' }, { name: `${chalk.yellow('●')} ${chalk.yellow.bold('JavaScript')} ${chalk.gray('- Classic JavaScript')}`, value: 'js' }, ], }, ]); answers.language = language; } // Git initialization selection if (options.git) { answers.initGit = true; } else if (options.skipGit) { answers.initGit = false; } else { const { initGit } = await inquirer.prompt([ { type: 'list', name: 'initGit', message: chalk.bold('Initialize Git repository?'), choices: [ { name: `${chalk.green('✓')} ${chalk.bold('Yes')} ${chalk.gray('- Initialize with Git')}`, value: true }, { name: `${chalk.red('✗')} ${chalk.bold('No')} ${chalk.gray('- Skip Git setup')}`, value: false }, ], default: 0, }, ]); answers.initGit = initGit; } return { language: answers.language, initGit: answers.initGit }; } function copyTemplate(src, dest) { try { // Ensure source exists if (!fs.existsSync(src)) { throw new Error(`Source template directory does not exist: ${src}`); } // Copy all files from template fs.copySync(src, dest, { filter: (src) => { // Skip node_modules but keep everything else return !src.includes('node_modules'); }, overwrite: true, errorOnExist: false }); // Verify the copy was successful by checking if essential files exist const packageJsonPath = path.join(dest, 'package.json'); if (!fs.existsSync(packageJsonPath)) { throw new Error(`Template copy failed - package.json not found in ${dest}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to copy template: ${errorMessage}`); } } // Async dependency installation for parallel execution async function installDependenciesAsync(dest) { const spinner = ora({ text: 'Installing dependencies...', spinner: 'dots', color: 'cyan' }).start(); return new Promise((resolve) => { // Use cross-platform npm command const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; const npmInstall = spawn(npmCommand, ['install'], { cwd: dest, stdio: 'ignore', shell: true // Important for Windows compatibility }); npmInstall.on('close', (code) => { if (code === 0) { spinner.succeed(chalk.green('Dependencies installed successfully!')); } else { spinner.fail(chalk.red('Failed to install dependencies. Run `npm install` manually.')); } resolve(); }); npmInstall.on('error', () => { spinner.fail(chalk.red('Failed to install dependencies. Run `npm install` manually.')); resolve(); }); }); } function updatePackageJson(dest) { const pkgPath = path.join(dest, 'package.json'); if (fs.existsSync(pkgPath)) { const pkg = fs.readJsonSync(pkgPath); pkg.name = projectName; fs.writeJsonSync(pkgPath, pkg, { spaces: 2 }); } } // Ultra-fast git initialization async function gitInitFast(dest) { const spinner = ora('Initializing Git repository...').start(); return new Promise((resolve) => { try { // Check if already a git repo if (fs.existsSync(path.join(dest, '.git'))) { spinner.info('Git repository already exists'); resolve(); return; } // Fast single command execution with better cross-platform support execSync('git init && git add . && git commit -m "Initial commit"', { cwd: dest, stdio: 'ignore', timeout: 10000, // Increased timeout for slower systems env: { ...process.env, // Remove potentially problematic environment variables GIT_TERMINAL_PROMPT: '0', GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME || 'Developer', GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL || 'dev@example.com', GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME || 'Developer', GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL || 'dev@example.com' } }); spinner.succeed('Git repository initialized'); resolve(); } catch (error) { // Silently skip git init if git is not available spinner.warn('Git not available - skipping repository initialization'); resolve(); } }); } function createEnvFile(targetDir) { const envContent = `MONGO=mongodb://localhost:27017/myapp\nPORT=5000\n`; const envPath = path.join(targetDir, '.env'); // Ensure the target directory exists fs.ensureDirSync(targetDir); fs.writeFileSync(envPath, envContent, 'utf-8'); } (async () => { // Simple CLI header console.log(); console.log(chalk.cyan.bold('🔧 Create Tin - Express Scaffold')); console.log(chalk.gray('Fast Express.js project setup')); console.log(); const template = await chooseTemplate(); const isTS = template.language === 'ts'; const initGit = template.initGit; const langColor = isTS ? chalk.blue : chalk.yellow; console.log(); // Add spacing before project creation // Try multiple possible template paths to handle different installation scenarios const templatePaths = [ path.join(__dirname, '..', 'templates', template.language), // Standard build structure path.join(__dirname, '..', '..', 'templates', template.language), // npm global install path.join(__dirname, 'templates', template.language), // Same directory path.join(process.cwd(), 'node_modules', 'create-tin', 'dist', 'templates', template.language), // Local install path.join(process.cwd(), 'node_modules', 'create-tin', 'templates', template.language), // Alternative structure ]; let templatePath = null; for (const possiblePath of templatePaths) { if (fs.existsSync(possiblePath)) { templatePath = possiblePath; break; } } if (!templatePath) { console.log(); console.log(chalk.red.bold(' ✖ Error: ') + chalk.red('Template not found!')); console.log(chalk.gray(' Searched paths:')); templatePaths.forEach(p => console.log(chalk.gray(` - ${p}`))); console.log(); process.exit(1); } const targetPath = path.resolve(process.cwd(), projectName); if (fs.existsSync(targetPath)) { console.log(); console.log(chalk.red.bold(' ✖ Error: ') + chalk.red(`Folder "${projectName}" already exists.`)); console.log(); process.exit(1); } const spinner = ora({ text: `Creating ${langColor(isTS ? 'TypeScript' : 'JavaScript')} project...`, spinner: 'dots', color: isTS ? 'blue' : 'yellow' }).start(); try { copyTemplate(templatePath, targetPath); updatePackageJson(targetPath); createEnvFile(targetPath); // Create .env file immediately after copying template spinner.succeed(chalk.green.bold(`✨ Created ${langColor.bold(projectName)} project!`)); } catch (e) { spinner.fail(chalk.red.bold('Project creation failed.')); console.error('Error details:', e); console.error(`Template path: ${templatePath}`); console.error(`Target path: ${targetPath}`); process.exit(1); } console.log(); // Add spacing // Create tasks array for parallel execution const tasks = [ installDependenciesAsync(targetPath) ]; // Add git initialization if requested if (initGit) { tasks.push(gitInitFast(targetPath)); } // Run dependency installation and git init in parallel for speed await Promise.all(tasks); // Clean, organized success message console.log(); console.log(chalk.green.bold(' 🎉 Project setup completed successfully!')); console.log(); console.log(chalk.bold(' 📋 Configuration Summary:')); console.log(` Language: ${langColor.bold(isTS ? 'TypeScript' : 'JavaScript')}`); console.log(` Git: ${initGit ? chalk.green.bold('✓ Initialized') : chalk.yellow.bold('✗ Skipped')}`); console.log(` Database: ${chalk.magenta.bold('MongoDB')} ${chalk.gray('(configurable in .env)')}`); console.log(` Port: ${chalk.cyan.bold('5000')} ${chalk.gray('(configurable in .env)')}`); console.log(); console.log(chalk.bold(' 🚀 Next steps:')); console.log(chalk.cyan(` cd ${projectName}`)); console.log(chalk.cyan(' npm run dev')); console.log(); console.log(chalk.gray(' Happy coding! 🎯')); console.log(); console.log(chalk.gray(' 📝 Usage: npx create-tin <project-name>')); console.log(); })();