From 2764fc689392aa4754c511a7fe5515373ef85c5f Mon Sep 17 00:00:00 2001 From: Josephat-S Date: Mon, 25 May 2026 11:44:00 +0200 Subject: [PATCH] edded loading spinner and graceful exit --- index.js | 11 ++- package-lock.json | 212 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + src/generate.js | 81 +++++++++++------- 4 files changed, 272 insertions(+), 33 deletions(-) diff --git a/index.js b/index.js index 267f721..667bcfc 100755 --- a/index.js +++ b/index.js @@ -43,7 +43,9 @@ async function runOpusifyWizard() { console.log(chalk.blue(` Welcome to ${chalk.magenta('Opusify')} — The Full-Stack Scaffold Engine v1.0.0`)); console.log(chalk.gray(' Generate production-ready apps with one command.\n')); - const config = await inquirer.prompt([ + let config; + try { + config = await inquirer.prompt([ // NEW STEP: Project Name { type: 'input', @@ -132,6 +134,13 @@ async function runOpusifyWizard() { default: true } ]); + } catch (error) { + if (error.name === 'ExitPromptError' || error.message?.includes('User force closed')) { + console.log(chalk.yellow('\nScaffold cancelled. Goodbye!')); + process.exit(0); + } + throw error; + } console.log('\n' + chalk.green('✔ Configuration collected successfully!')); diff --git a/package-lock.json b/package-lock.json index 984d2fe..1735928 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "chalk": "^5.6.2", "handlebars": "^4.7.9", "inquirer": "^13.4.3", + "ora": "^9.4.0", "tiged": "^2.12.7" }, "bin": { @@ -651,6 +652,33 @@ "node": ">=10" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -1121,6 +1149,18 @@ "integrity": "sha512-s+kNWQuI3mo9OALw0HJ6YGmMbLqEufCh2nX/zzV5CrICQ/y4AwPxM+6TIiF9ItFCHXFCyM/BfCCmN57NTIJuPg==", "license": "MIT" }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1359,6 +1399,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -1369,6 +1421,18 @@ "node": ">=8" } }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1469,6 +1533,34 @@ "dev": true, "license": "MIT" }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -1582,6 +1674,21 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -1600,6 +1707,28 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.0.tgz", + "integrity": "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==", + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.2", + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1741,6 +1870,22 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -1860,6 +2005,61 @@ "node": ">=0.10.0" } }, + "node_modules/stdin-discarder": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.2.tgz", + "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2078,6 +2278,18 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index b46fec4..0a68bb3 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "chalk": "^5.6.2", "handlebars": "^4.7.9", "inquirer": "^13.4.3", + "ora": "^9.4.0", "tiged": "^2.12.7" }, "devDependencies": { diff --git a/src/generate.js b/src/generate.js index 4db18a7..63b64ee 100644 --- a/src/generate.js +++ b/src/generate.js @@ -1,6 +1,7 @@ import fs from 'fs'; import path from 'path'; import chalk from 'chalk'; +import ora from 'ora'; import tiged from 'tiged'; import Handlebars from 'handlebars'; import { execSync } from 'child_process'; @@ -20,7 +21,7 @@ function getAllFiles(dirPath, arrayOfFiles = []) { } export async function generateProject(config) { - console.log(chalk.cyan('\n⚙️ Starting the File Generation Engine...')); + console.log(chalk.cyan('\n⚙️ Starting the File Generation Engine...')); let projectName = config.projectName; let projectPath = path.join(process.cwd(), projectName); @@ -34,25 +35,36 @@ export async function generateProject(config) { config.projectName = projectName; // Update config with final name // 2. Check for Local vs GitHub - const localTemplatePath = path.join(process.cwd(), 'templates', config.template, config.architecture); - + const localTemplatePath = path.join( + process.cwd(), + 'templates', + config.template, + config.architecture, + ); + try { if (fs.existsSync(localTemplatePath)) { // 🟢 DEVELOPMENT MODE: Local folder found - console.log(chalk.blue(`📥 DEV MODE: Using local template from ${localTemplatePath}`)); + const spinner = ora('DEV MODE: Copying local template...').start(); fs.cpSync(localTemplatePath, projectPath, { recursive: true }); + spinner.succeed(`Files copied to ./${projectName}`); } else { // 🔵 PRODUCTION MODE: Fetch from GitHub const repoURI = `Ebyte-Lab/opusify-templates/${config.template}/${config.architecture}`; - console.log(chalk.blue(`📥 PROD MODE: Fetching from GitHub (${repoURI})...`)); - - const emitter = tiged(repoURI, { disableCache: true, force: true }); - await emitter.clone(projectPath); + const spinner = ora(`Fetching template from GitHub (${repoURI})...`).start(); + + try { + const emitter = tiged(repoURI, { disableCache: true, force: true }); + await emitter.clone(projectPath); + spinner.succeed(`Files copied to ./${projectName}`); + } catch (fetchError) { + spinner.fail('Failed to fetch template from GitHub.'); + throw fetchError; + } } - console.log(chalk.green(`✔ Files copied to ./${projectName}`)); // 3. TRANSFORM PHASE: Process Handlebars Tags - console.log(chalk.cyan('🪄 Compiling template tags...')); + const compileSpinner = ora('Compiling template tags...').start(); const allFiles = getAllFiles(projectPath); for (const file of allFiles) { @@ -65,42 +77,47 @@ export async function generateProject(config) { } } } - console.log(chalk.green('✔ Template customization complete!')); + compileSpinner.succeed('Template customization complete!'); // 4. Save the config blueprint const configFilePath = path.join(projectPath, 'opusify.config.json'); fs.writeFileSync(configFilePath, JSON.stringify(config, null, 2)); - // 5. AUTOMATION PHASE: Install Dependencies and Git - console.log(chalk.cyan('\n📦 Installing dependencies (this might take a minute)...')); + // 5. AUTOMATION PHASE: Install Dependencies + const installSpinner = ora('Installing dependencies (this might take a minute)...').start(); try { - execSync('npm install', { cwd: projectPath, stdio: 'inherit' }); - console.log(chalk.green('✔ Dependencies installed successfully!')); + execSync('npm install', { cwd: projectPath, stdio: 'pipe' }); + installSpinner.succeed('Dependencies installed successfully!'); + } catch (installError) { + installSpinner.fail('Could not install dependencies. You may need to run npm install manually.'); + } - // Check if the user selected 'Yes' for Git Initialization - if (config.initGit) { - console.log(chalk.cyan('\n🐙 Initializing Git repository...')); + // 6. Git Initialization + if (config.initGit) { + const gitSpinner = ora('Initializing Git repository...').start(); + try { execSync('git init', { cwd: projectPath, stdio: 'ignore' }); execSync('git add .', { cwd: projectPath, stdio: 'ignore' }); - execSync('git commit -m "feat: initial commit from Opusify CLI 🚀"', { cwd: projectPath, stdio: 'ignore' }); - console.log(chalk.green('✔ Git initialized!')); - } else { - console.log(chalk.gray('\n⏭️ Skipping Git initialization.')); + execSync('git commit -m "feat: initial commit from Opusify CLI 🚀"', { + cwd: projectPath, + stdio: 'ignore', + }); + gitSpinner.succeed('Git initialized!'); + } catch (gitError) { + gitSpinner.fail('Could not initialize Git. You may need to do it manually.'); } - - } catch (automationError) { - console.log(chalk.yellow('\n⚠️ Note: Could not complete npm install or git setup automatically. You may need to do it manually.')); + } else { + console.log(chalk.gray('\n⏭️ Skipping Git initialization.')); } - - // 6. Final Success Message + + // 7. Final Success Message console.log(chalk.magenta(`\n🎉 Project ${projectName} is ready!`)); - console.log(chalk.white(`\nNext steps:`)); + console.log(chalk.white('\nNext steps:')); console.log(chalk.cyan(` cd ${projectName}`)); - console.log(chalk.cyan(` npm run dev\n`)); - + console.log(chalk.cyan(' npm run dev\n')); } catch (error) { - console.log(chalk.red(`\n🚨 Generation failed.`)); + console.log(chalk.red('\n🚨 Generation failed.')); console.log(chalk.gray(error.message)); if (fs.existsSync(projectPath)) fs.rmSync(projectPath, { recursive: true, force: true }); } -} \ No newline at end of file +}