UNPKG

reactango

Version:

A CLI tool that simplify React and Django Implementation.

351 lines (325 loc) 11.1 kB
import inquirer from "inquirer"; import chalk from "chalk"; import ora from "ora"; import { execSync } from "child_process"; import fs from "fs-extra"; import path from "path"; import os from "os"; import { EMOJI, TEMPLATE_REPO_URL } from "../utils/constants.js"; import { log } from "../utils/logger.js"; import { isGitAvailable, executeCommand } from "../utils/system.js"; import { runProjectSetup } from "../installers/index.js"; export async function handleCreateProject(projectName, options) { const targetDir = path.resolve(projectName); if (fs.existsSync(targetDir)) { log.error( `Directory '${targetDir}' already exists. Please choose a different name or remove the existing directory.` ); process.exit(1); } const spinner = ora( `${EMOJI.CLONE} Cloning ReactTangoTemplate into '${chalk.yellow( projectName )}'...` ).start(); try { let gitCloneCommand = `git clone`; if (options.branch) { gitCloneCommand += ` --branch ${options.branch}`; } gitCloneCommand += ` ${TEMPLATE_REPO_URL} "${targetDir}"`; execSync(gitCloneCommand, { stdio: "pipe" }); spinner.succeed( `Template cloned successfully into '${chalk.yellow(targetDir)}'.` ); } catch (error) { spinner.fail("Failed to clone template repository."); if (error.stderr) console.error(chalk.red(error.stderr.toString())); process.exit(1); } const gitDirPath = path.join(targetDir, ".git"); if (fs.existsSync(gitDirPath)) { log.step("Removing template's .git directory..."); try { fs.removeSync(gitDirPath); console.log( chalk.green.bold(`${EMOJI.SPARKLES} Template .git directory removed.`) ); } catch (error) { log.warning( `Could not remove .git directory: ${error.message}. Please remove it manually.` ); } } let shouldInitializeGit = false; if (isGitAvailable()) { if (options.initGit) { shouldInitializeGit = true; log.step("--init-git flag used: Forcing git initialization."); } else if (options.noInitGit) { shouldInitializeGit = false; console.log( chalk.yellow.bold( `${EMOJI.SKIP} --no-init-git flag used: Skipping git initialization.` ) ); } else { try { const answers = await inquirer.prompt([ { type: "confirm", name: "initGit", message: `${EMOJI.PROMPT} Initialize a new git repository in the project?`, default: true, }, ]); shouldInitializeGit = answers.initGit; } catch (error) { log.warning( `Could not display interactive git prompt (${error.message}). Defaulting to no git initialization.` ); shouldInitializeGit = false; } } } else { if (options.initGit) { log.warning( "--init-git flag used, but Git command not found. Cannot initialize repository." ); } else { log.warning( "Git command not found. Skipping git repository initialization." ); } } if (shouldInitializeGit) { console.log( chalk.blue.bold( `\n${EMOJI.GIT} Initializing a new git repository in '${chalk.yellow( targetDir )}'...` ) ); if ( executeCommand("git init", { cwd: targetDir, errorMessage: "Failed to initialize git repository.", }) ) { console.log( chalk.green.bold(`${EMOJI.SPARKLES} New git repository initialized.`) ); log.step("Adding files to the new repository..."); if ( executeCommand("git add .", { cwd: targetDir, errorMessage: "Failed to add files to git.", }) ) { log.step("Making initial commit..."); const initialCommitMessage = `Initial commit: Bootstrap '${projectName}' from ReactTangoTemplate`; if ( executeCommand(`git commit -m "${initialCommitMessage}"`, { cwd: targetDir, errorMessage: "Failed to make initial commit.", }) ) { log.success(`Initial commit made: "${initialCommitMessage}"`); } } } } let ranAnyInstallation = false; let installChoicesMade = []; // "backend", "frontend" if (!options.skipAllInstall) { const packageJsonExists = fs.existsSync( path.join(targetDir, "package.json") ); const requirementsTxtExists = fs.existsSync( path.join(targetDir, "requirements.txt") ); const availableInstallOptions = []; if (requirementsTxtExists && packageJsonExists) { availableInstallOptions.push({ name: `${EMOJI.ALL} Install All Dependencies (Backend + Frontend)`, value: "all", }); } if (requirementsTxtExists) { availableInstallOptions.push({ name: `${EMOJI.PYTHON} Backend Only (Python with venv)`, value: "backend", }); } if (packageJsonExists) { availableInstallOptions.push({ name: `${EMOJI.NODE} Frontend Only (Node.js with pnpm)`, value: "frontend", }); } availableInstallOptions.push({ name: `${EMOJI.NONE} Install None (Skip all installations)`, value: "none", }); if ( availableInstallOptions.length > 1 && (packageJsonExists || requirementsTxtExists) ) { if (options.installAll) { log.step( "--install-all flag used: Proceeding with all available installations." ); if (requirementsTxtExists) installChoicesMade.push("backend"); if (packageJsonExists) installChoicesMade.push("frontend"); } else { try { console.log(chalk.magenta.bold("=".repeat(70))); console.log( chalk.magenta.bold( `${EMOJI.INSTALL} DEPENDENCY INSTALLATION OPTIONS ${EMOJI.INSTALL}` ) ); console.log(chalk.magenta.bold("=".repeat(70))); const answers = await inquirer.prompt([ { type: "list", name: "installChoice", message: `${EMOJI.PROMPT} What dependencies would you like to install?`, choices: availableInstallOptions, default: requirementsTxtExists && packageJsonExists ? "all" : requirementsTxtExists ? "backend" : packageJsonExists ? "frontend" : "none", }, ]); const selectedOption = answers.installChoice; if (selectedOption === "all") { if (requirementsTxtExists) installChoicesMade.push("backend"); if (packageJsonExists) installChoicesMade.push("frontend"); log.info( `Selected: ${chalk.green.bold("Install All Dependencies")}` ); } else if (selectedOption === "none") { installChoicesMade = []; log.info( `Selected: ${chalk.yellow.bold("Skip All Installations")}` ); } else { installChoicesMade = [selectedOption]; const optionName = selectedOption === "backend" ? "Backend" : "Frontend"; log.info(`Selected: ${chalk.cyan.bold(optionName + " Only")}`); } } catch (error) { log.warning( `Could not display interactive install prompt (${error.message}). Skipping installations.` ); installChoicesMade = []; } } if (installChoicesMade.length > 0) { const installBe = installChoicesMade.includes("backend"); const installFe = installChoicesMade.includes("frontend"); const useVenvForBe = !options.noVenv && installBe; if ( await runProjectSetup(targetDir, installBe, installFe, useVenvForBe) ) { ranAnyInstallation = true; } } else if (!options.installAll) { console.log( chalk.yellow.bold( `\n${EMOJI.SKIP} No dependencies selected for installation.` ) ); } } else if ( availableInstallOptions.length === 1 && availableInstallOptions[0].value === "none" ) { log.info( "No dependency manifest files (requirements.txt, package.json) found. Skipping installation phase." ); } } else { console.log( chalk.yellow.bold( `\n${EMOJI.SKIP} --skip-all-install flag used. All dependency installations are skipped.` ) ); } console.log(chalk.green.bold("\n" + "=".repeat(70))); log.party( `PROJECT '${chalk.yellow.bold( projectName.toUpperCase() )}' CREATED SUCCESSFULLY! ${EMOJI.ROCKET}` ); console.log(chalk.green.bold("=".repeat(70))); console.log(chalk.blue.bold(`\n${EMOJI.NEXT_STEPS} NEXT STEPS:`)); console.log( ` ${chalk.cyan.bold("1.")} ${chalk.yellow(`cd ${projectName}`)}` ); let stepCounter = 2; const installedBackend = ranAnyInstallation && installChoicesMade.includes("backend"); // const installedFrontend = ranAnyInstallation && installChoicesMade.includes("frontend"); // Not used directly const suggestManualBackend = fs.existsSync(path.join(targetDir, "requirements.txt")) && !installedBackend; const suggestManualFrontend = fs.existsSync(path.join(targetDir, "package.json")) && !(ranAnyInstallation && installChoicesMade.includes("frontend")); if ( !options.skipAllInstall && (suggestManualBackend || suggestManualFrontend) ) { console.log( ` ${chalk.cyan.bold(`${stepCounter++}.`)} ${chalk.blue.bold( `${EMOJI.GEAR} Install dependencies manually if needed:` )}` ); if (suggestManualBackend) { console.log( ` ${chalk.magenta.bold(`${EMOJI.PYTHON} Backend:`)} ${chalk.cyan( "python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt" )} (or equivalent for your OS)` ); } if (suggestManualFrontend) { console.log( ` ${chalk.green.bold(`${EMOJI.NODE} Frontend:`)} ${chalk.cyan( "pnpm install" )}` ); } } if (installedBackend && !options.noVenv) { // Check if backend was installed and venv was used const isWindows = os.platform() === "win32"; const activateCmd = isWindows ? "venv\\Scripts\\activate" : "source venv/bin/activate"; console.log( ` ${chalk.cyan.bold(`${stepCounter++}.`)} ${chalk.magenta.bold( `${EMOJI.PYTHON} Activate Python virtual environment:` )} ${chalk.cyan(activateCmd)}` ); } console.log( ` ${chalk.cyan.bold(`${stepCounter++}.`)} ${chalk.green.bold( `${EMOJI.ROCKET} Start development server (if applicable):` )} ${chalk.cyan("pnpm run dev")}` ); console.log( chalk.dim( "\n For more details, check the README.md inside your new project." ) ); console.log( chalk.yellow.bold(`\n${EMOJI.SPARKLES} Happy coding! ${EMOJI.SPARKLES}`) ); }