diff --git a/index.js b/index.js index ea43e72..46398ba 100755 --- a/index.js +++ b/index.js @@ -74,6 +74,13 @@ function printBanner() { async function createAction(projectName, options) { printBanner(); + // 1. Validate project name if provided as an argument + if (projectName && !/^[a-z0-9-]+$/.test(projectName)) { + console.log(chalk.red(`\n✖ Invalid project name: "${projectName}"`)); + console.log(chalk.gray(' Suggested fix: Please use only lowercase letters, numbers, and hyphens (e.g., my-awesome-app).')); + process.exit(1); + } + // Validate flags early if (options.template && !VALID_TEMPLATES.includes(options.template)) { console.log(chalk.red(`\n✖ Invalid template: "${options.template}"`)); @@ -292,6 +299,28 @@ async function createAction(projectName, options) { token: options.token || process.env.OPUSIFY_GITHUB_TOKEN || process.env.GITHUB_TOKEN, }; + // 2. Warn if selecting Vite SPA for E-Commerce (SEO concern) + if (config.template === 'ecommerce' && config.architecture.includes('vite')) { + console.log(chalk.yellow('\n⚠️ WARNING: You selected Vite SPA for an E-Commerce template.')); + console.log(chalk.gray(' Vite generates a purely Client-Side application (No SSR).')); + console.log(chalk.gray(' This severely impacts SEO, which is crucial for E-Commerce stores.')); + console.log(chalk.gray(' Suggested fix: We highly recommend using Next.js App Router instead.')); + + // If not in --yes mode, ask for confirmation + if (!options.yes) { + const { proceed } = await inquirer.prompt([{ + type: 'confirm', + name: 'proceed', + message: chalk.yellow('Do you still want to proceed with Vite?'), + default: false + }]); + if (!proceed) { + console.log(chalk.yellow('\nScaffold cancelled. Please run the command again.')); + process.exit(0); + } + } + } + console.log('\n' + chalk.green('✔ Configuration collected successfully!')); await generateProject(config); } diff --git a/src/generate.js b/src/generate.js index cf44883..fc4a5ee 100644 --- a/src/generate.js +++ b/src/generate.js @@ -87,10 +87,21 @@ export async function generateProject(config) { spinner.succeed(`Files copied to ./${projectName}`); } catch (fetchError) { spinner.fail(`Failed to fetch template from GitHub: ${repoURI}`); - if (!config.token) { - console.log(chalk.yellow('\n⚠️ Hint: If this repository is private, you must provide a GitHub token using --token or set the OPUSIFY_GITHUB_TOKEN environment variable.')); + + // SPECIFIC NETWORK & GITHUB ERROR HANDLING + const errStr = fetchError.toString().toLowerCase(); + if (errStr.includes('could not resolve') || errStr.includes('econnrefused') || errStr.includes('offline') || errStr.includes('network')) { + console.log(chalk.red(' ✖ Error: Could not reach GitHub. Check your internet connection.')); + console.log(chalk.gray(' Suggested fix: Ensure you are connected to the internet and try again.')); + } else if (errStr.includes('could not find commit hash') || errStr.includes('404')) { + console.log(chalk.red(' ✖ Error: The specified template or repository does not exist.')); + if (!config.token) { + console.log(chalk.yellow(' ⚠️ Hint: If this repository is private, you must provide a GitHub token using --token or set the OPUSIFY_GITHUB_TOKEN environment variable.')); + } + } else { + console.log(chalk.red(` ✖ Error details: ${fetchError.message}`)); } - throw fetchError; + throw new Error('FETCH_FAILED'); } } @@ -125,6 +136,15 @@ export async function generateProject(config) { if (config.noInstall) { console.log(chalk.gray('\n⏭️ Skipping npm install (--no-install).')); } else { + + // CHECK FOR EXISTING NODE_MODULES + const nodeModulesPath = path.join(projectPath, 'node_modules'); + if (fs.existsSync(nodeModulesPath)) { + console.log(chalk.yellow('\n⚠️ WARNING: A node_modules directory already exists in the target directory.')); + console.log(chalk.gray(' This can happen if you are testing locally and left it inside your template folder.')); + console.log(chalk.gray(' Suggested fix: Delete node_modules from your template source to avoid copy bloat.')); + } + const installSpinner = ora({ text: 'Installing dependencies (this might take a minute)...', spinner: 'squareCorners', @@ -134,7 +154,9 @@ export async function generateProject(config) { 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.'); + installSpinner.fail('Could not install dependencies.'); + console.log(chalk.red(` ✖ NPM Error: ${installError.message}`)); + console.log(chalk.gray(' Suggested fix: Run "npm install" manually inside the project folder to see detailed errors.')); } } @@ -154,7 +176,8 @@ export async function generateProject(config) { }); gitSpinner.succeed('Git initialized!'); } catch (gitError) { - gitSpinner.fail('Could not initialize Git. You may need to do it manually.'); + gitSpinner.fail('Could not initialize Git.'); + console.log(chalk.gray(' Suggested fix: Ensure git is installed on your system or run "git init" manually.')); } } else { console.log(chalk.gray('\n⏭️ Skipping Git initialization.')); @@ -167,7 +190,28 @@ export async function generateProject(config) { console.log(chalk.cyan(' npm run dev\n')); } catch (error) { console.log(chalk.red('\n🚨 Generation failed.')); - console.log(chalk.gray(error.message)); - if (fs.existsSync(projectPath)) fs.rmSync(projectPath, { recursive: true, force: true }); + + // Detailed System Error Classification + if (error.code === 'ENOSPC') { + console.log(chalk.red(' ✖ Error: Not enough disk space.')); + console.log(chalk.gray(' Suggested fix: Free up some space on your hard drive and try again.')); + } else if (error.code === 'EACCES' || error.code === 'EPERM') { + console.log(chalk.red(' ✖ Error: Permission denied.')); + console.log(chalk.gray(' Suggested fix: Check your folder permissions or run your terminal as an administrator/sudo.')); + } else if (error.message !== 'FETCH_FAILED') { + // Print generic errors if it's not one we already handled above + console.log(chalk.gray(` Details: ${error.message}`)); + } + + // AUTOMATED CLEANUP + if (fs.existsSync(projectPath)) { + console.log(chalk.yellow(`\n🧹 Cleaning up partial project directory: ./${projectName}...`)); + try { + fs.rmSync(projectPath, { recursive: true, force: true }); + console.log(chalk.green(' ✔ Cleanup complete.')); + } catch (cleanupError) { + console.log(chalk.red(` ✖ Failed to clean up directory. You may need to delete ./${projectName} manually.`)); + } + } } } \ No newline at end of file