skelecli
Version:
ππβ€οΈA simple CLI to generate a blueprint for a new projectπβ π¨βπ
193 lines (171 loc) β’ 6.27 kB
JavaScript
#!/usr/bin/env node
import inquirer from 'inquirer';
import fs from 'fs';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';
import welcome from 'conwelcome';
import createDirectoryContents from './createDirectoryContents.js';
import installDependencies from './installDependencies.js';
import checkForUpdates from './checkUpdates.js';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const packageJson = require('./package.json');
const CURRENT_VERSION = packageJson.version;
const PACKAGE_NAME = packageJson.name;
const CURR_DIR = process.cwd();
const __dirname = dirname(fileURLToPath(import.meta.url));
/**
* Fetches available template choices from the templates directory.
* @returns {string[]} List of available template choices.
*/
const getTemplateChoices = () => {
const templatePath = path.join(__dirname, 'templates');
if (!fs.existsSync(templatePath)) {
console.error('β Templates directory not found. Please ensure templates exist.');
process.exit(0);
}
const templates = fs.readdirSync(templatePath);
if (templates.length === 0) {
console.error('β No templates available. Please add templates to the templates folder.');
process.exit(0);
}
return templates;
};
/**
* Displays a welcome message.
*/
const displayWelcomeMessage = () => {
welcome({
title:`π₯ ${PACKAGE_NAME} π₯`,
tagLine: 'by Sumangal Karan π»',
description: 'β€οΈπ SkeleCLI is the best CLI tool for creating new projects! π―β¨',
bgColor: '#fadc5e',
color: '#000000',
bold: true,
clear: true,
version: `${CURRENT_VERSION}`,
});
};
/**
* Prompts user for project details.
* @returns {Promise<Object>} User's selected project template and name.
*/
const askProjectDetails = async () => {
return await inquirer.prompt([
{
name: 'projectChoice',
type: 'list',
message: 'π What project template would you like to generate?',
choices: getTemplateChoices(),
},
{
name: 'projectName',
type: 'input',
message: 'π Enter a project name:',
validate: input =>
/^([A-Za-z\-\_\d\.])+$/.test(input) || 'β Project name may only include letters, numbers, underscores, and dashes.',
},
]);
};
/**
* Handles existing project folder.
* @param {string} projectPath - The project directory path.
* @returns {Promise<boolean>} Whether to continue creating the project.
*/
const handleExistingProject = async (projectPath) => {
const { action } = await inquirer.prompt([
{
name: 'action',
type: 'list',
message: `π¨ The folder "${path.basename(projectPath)}" already exists. What would you like to do?`,
choices: [
{ name: 'β Remove all files and create a new project', value: 'remove' },
{ name: 'βοΈ Overwrite existing files (keep other files)', value: 'overwrite' },
{ name: 'πͺ Stop and exit', value: 'stop' },
],
},
]);
if (action === 'remove') {
console.log(`ποΈ Removing existing project folder: ${path.basename(projectPath)}`);
fs.rmSync(projectPath, { recursive: true, force: true });
fs.mkdirSync(projectPath, { recursive: true });
return true;
}
if (action === 'overwrite') {
console.log('βοΈ Overwriting files while keeping the existing structure.');
return true;
}
console.log('π« Operation canceled.');
process.exit(0);
};
/**
* Asks user whether to install dependencies automatically.
* @returns {Promise<boolean>} Whether to install dependencies.
*/
const askInstallDependencies = async () => {
const { install } = await inquirer.prompt([
{
name: 'install',
type: 'confirm',
message: 'π¦ Do you want to install dependencies automatically?',
default: true, // Default to Yes
},
]);
return install;
};
/**
* Creates the project from the selected template.
* @param {string} templateName - The chosen template name.
* @param {string} projectName - The project name.
*/
const createProject = async (templateName, projectName) => {
const templatePath = path.join(__dirname, 'templates', templateName);
const projectPath = projectName === '.' ? CURR_DIR : path.join(CURR_DIR, projectName);
if (!fs.existsSync(templatePath)) {
console.error('β Selected template does not exist. Please check your π templates folder.');
process.exit(0);
}
if (fs.existsSync(projectPath)) {
await handleExistingProject(projectPath);
} else {
fs.mkdirSync(projectPath, { recursive: true });
}
try {
createDirectoryContents(templatePath, projectPath);
console.log(`\nβ
Project ${projectName === '.' ? 'created in current directory' : projectName} created successfully!`);
const installDeps = await askInstallDependencies();
if (installDeps) {
console.log('π¦ Installing dependencies... β³ ');
installDependencies(projectPath);
} else {
console.log(`\nπ To install dependencies manually, run:\n`);
console.log(` π cd ${projectName}\n`);
console.log(` π pnpm install\n`);
console.log(` π bun install\n`);
console.log(` π npm install\n`);
console.log(` π yarn install\n`);
}
} catch (err) {
console.error('β Error creating project:', err);
process.exit(0);
}
};
// π Run CLI Tool
(async () => {
try {
console.log('π Checking for updates... β³');
const updateAvailable = await checkForUpdates(); // Wait for update check
if (updateAvailable) {
console.log('π Update completed. Please restart the CLI.');
process.exit(0); // Exit after update
}
displayWelcomeMessage();
const { projectChoice, projectName } = await askProjectDetails();
await createProject(projectChoice, projectName);
} catch (error) {
if (error.message.includes('User force closed the prompt')) {
console.log('π« Operation canceled by user.');
process.exit(0); // β
Exit normally without error
}
}
})();