UNPKG

create-mytech

Version:

Create Your Own Techstack with basic boilerplate code.

527 lines (507 loc) 17.5 kB
#!/usr/bin/env node // src/index.ts import fs5 from "fs-extra"; import path7 from "path"; // helpers/CreateMERN.ts import fs from "fs-extra"; import path from "path"; import ora from "ora"; import { execSync } from "child_process"; import chalk from "chalk"; async function createMERNBoilerplate(projectPath, answers) { const { installDeps } = answers; const spinner = ora(); const serverPath = path.join(projectPath, "server"); const clientPath = path.join(projectPath, "client"); const srcPath = path.join(clientPath, "src"); await fs.ensureDir(serverPath); await fs.ensureDir(clientPath); await fs.ensureDir(srcPath); spinner.start("Creating package.json for backend..."); await createPackageJson(serverPath, "server"); spinner.succeed(chalk.green("Backend package.json created")); spinner.start("Creating package.json for frontend..."); await createPackageJson(clientPath, "client"); spinner.succeed(chalk.green("Frontend package.json created")); spinner.start("Creating server files..."); await createServerFiles(serverPath); spinner.succeed(chalk.green("Server Files Created")); spinner.start("Creating React frontend files..."); await createReactFiles(srcPath); spinner.succeed(chalk.green("React frontend files created")); if (installDeps) { spinner.start("Installing backend dependencies..."); execSync("npm install", { cwd: serverPath, stdio: "inherit" }); spinner.succeed(); spinner.start("Installing frontend dependencies..."); execSync("npm install", { cwd: clientPath, stdio: "inherit" }); spinner.succeed(chalk.green("Installed.....")); } else { console.log( "You can install the dependencies later by running 'npm install' in both the 'server' and 'client' directories." ); } chalk.green("MERN boilerplate setup complete!"); } async function createPackageJson(dir, type) { const packageJsonContent = { name: type === "server" ? "mern-server" : "mern-client", version: "1.0.0", scripts: type === "server" ? { start: "node server.js", dev: "nodemon server.js" } : { start: "react-scripts start", build: "react-scripts build", test: "react-scripts test", eject: "react-scripts eject" }, dependencies: type === "server" ? {} : { react: "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.0.0", axios: "^0.21.1" }, devDependencies: type === "server" ? { nodemon: "^2.0.7" } : {} }; await fs.writeJson(path.join(dir, "package.json"), packageJsonContent, { spaces: 2 }); } async function createServerFiles(serverPath) { const serverFileContent = ` require('dotenv').config(); const express = require('express'); const mongoose = require('mongoose'); const cors = require('cors'); const app = express(); const PORT = process.env.PORT || 5000; // Middleware app.use(cors()); app.use(express.json()); // MongoDB connection mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true }).then(() => { console.log("MongoDB connected..."); }).catch(err => console.error("Could not connect to MongoDB...", err)); // Sample route app.get('/', (req, res) => res.send("API running")); // Start server app.listen(PORT, () => console.log(\`Server running on port \${PORT}\`)); `; await fs.writeFile(path.join(serverPath, "server.js"), serverFileContent); const envContent = `MONGO_URI=your_mongodb_connection_string_here`; await fs.writeFile(path.join(serverPath, ".env"), envContent); } async function createReactFiles(srcPath) { const appFileContent = ` import React from 'react'; import './globals.css'; function App() { return ( <div className="App"> <h1>MERN Boilerplate</h1> </div> ); } export default App; `; const globalsCssContent = ` body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } `; await fs.writeFile(path.join(srcPath, "App.js"), appFileContent); await fs.writeFile(path.join(srcPath, "globals.css"), globalsCssContent); const indexFileContent = ` import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import './globals.css'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); `; await fs.writeFile(path.join(srcPath, "index.js"), indexFileContent); } // helpers/CreateNextTailwind.ts import fs4 from "fs-extra"; import path5 from "path"; import ora3 from "ora"; import chalk4 from "chalk"; import { execSync as execSync2 } from "child_process"; // helpers/setupPrisma.ts import fs2 from "fs-extra"; import path3 from "path"; import ora2 from "ora"; import chalk2 from "chalk"; // consts.ts import path2 from "path"; import { fileURLToPath } from "url"; var __filename = fileURLToPath(import.meta.url); var title_text_ASCII = ` \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D `; var distPath = path2.dirname(__filename); var PKG_ROOT = path2.join(distPath, "../"); // helpers/setupPrisma.ts async function setupPrisma({ projectPath, orm, database, authentication, prismasrcpath }) { const spinner = ora2(); if (orm === "Prisma") { spinner.start("Setting up Prisma..."); const prismaSchemaSrcPath = path3.join( PKG_ROOT, "templates/extras/prisma/schema", prismasrcpath ); const packageJsonPath = path3.join(projectPath, "package.json"); let prismaSchema = await fs2.readFileSync(prismaSchemaSrcPath, "utf-8"); prismaSchema = prismaSchema.replace( 'provider = "postgresql"', `provider = "${database === "MySQL" ? "mysql" : "postgresql"}"` ); const schemaDest = path3.join(projectPath, "prisma/schema.prisma"); fs2.mkdirSync(path3.dirname(schemaDest), { recursive: true }); fs2.writeFileSync(schemaDest, prismaSchema); spinner.succeed( chalk2.green("Successfully copied Prisma Schema Files.") ); try { const packageJson = await fs2.readJson(packageJsonPath); packageJson.dependencies = { ...packageJson.dependencies, prisma: "^4.0.0", "@prisma/client": "^4.0.0" }; if (authentication === "NextAuth") { packageJson.dependencies = { ...packageJson.dependencies, "next-auth": "4.24.10", bcrypt: "5.1.0" }; packageJson.devDependencies = { ...packageJson.devDependencies, "@types/bcrypt": "5.0.0" }; } await fs2.writeJson(packageJsonPath, packageJson, { spaces: 2 }); } catch (error) { spinner.fail( chalk2.red( "Failed to update package.json with Prisma dependencies." ) ); console.error(error); return; } spinner.succeed( chalk2.green(`Successfully setup ${database} database service.`) ); } else { console.log( chalk2.yellow("Prisma setup skipped based on selected options.") ); } } // helpers/writePackageJSON.ts import path4 from "path"; import fs3 from "fs-extra"; var writePackageJSON = async (projectPath, dependencies, devDependencies) => { const packageJsonPath = path4.join(projectPath, "package.json"); const packageJson = await fs3.readJson(packageJsonPath); packageJson.dependencies = { ...packageJson.dependencies, dependencies }; packageJson.devDependencies = { ...packageJson.devDependencies, devDependencies }; await fs3.writeJson(packageJsonPath, packageJson, { spaces: 2 }); }; // utils/logger.ts import chalk3 from "chalk"; var logger = { error(...args) { console.log(chalk3.red(...args)); }, warn(...args) { console.log(chalk3.yellow(...args)); }, info(...args) { console.log(chalk3.cyan(...args)); }, success(...args) { console.log(chalk3.green(...args)); } }; // helpers/CreateNextTailwind.ts async function createNextTailwindBoilerplate(projectPath, options) { const { orm, database, authentication, installDeps } = options; const spinner = ora3(); const authConfig = { NextAuth: { templatePath: "templates/extras/app-with-auth", prismasrcpath: "next-auth.prisma", message: "Successfully setup boilerplate for NextAuth." }, "Hard-coded": { templatePath: "templates/extras/app-with-hard-coded", prismasrcpath: "hard-coded.prisma", message: "Successfully setup boilerplate with Hard-Coded Authentication." }, None: { templatePath: "templates/next-tailwind", prismasrcpath: "base.prisma", message: "Successfully scaffolded without authentication." } }; try { spinner.start("Setting up your project ......."); await fs4.copySync( path5.join(PKG_ROOT, "templates/next-tailwind"), projectPath ); const auth = authConfig[authentication]; if (!auth) { throw new Error( `Unsupported authentication type: ${authentication}` ); } if (auth.templatePath) { if (authentication === "NextAuth") { writePackageJSON( projectPath, { "next-auth": "4.24.10", bcrypt: "5.1.0" }, { "@types/bcrypt": "5.0.0" } ); } if (authentication === "Hard-coded") { writePackageJSON( projectPath, { bcryptjs: "^2.4.3", "@nextui-org/react": "^2.2.10", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-slot": "^1.0.2", axios: "^1.6.8", "react-hot-toast": "^2.4.1", "tailwindcss-animate": "^1.0.7", "class-variance-authority": "^0.7.0", "framer-motion": "^11.0.22", jsonwebtoken: "^9.0.2", "lucide-react": "^0.365.0", nodemailer: "^6.9.12" }, { "@types/jsonwebtoken": "^9.0.6" } ); } await fs4.copySync( path5.join(PKG_ROOT, auth.templatePath), projectPath ); spinner.succeed(chalk4.green(auth.message)); } if (orm === "Prisma" && auth.prismasrcpath) { if (database === "None") { logger.warn( "Prisma requires a database so selecting PostgreSQL as default." ); } await setupPrisma({ projectPath, orm, database, authentication, prismasrcpath: auth.prismasrcpath }); } if (installDeps) { spinner.start("Installing dependencies..."); execSync2("npm install", { cwd: projectPath, stdio: "inherit" }); spinner.succeed(chalk4.green("Dependencies installed.")); } else { console.log( chalk4.bgYellowBright( "You can install dependencies later by running 'npm install' inside the project directory." ) ); } } catch (error) { spinner.fail(chalk4.red("An error occurred during the setup process.")); console.error(error.message); } } // src/cli.ts import { Command } from "commander"; import path6 from "path"; import inquirer from "inquirer"; var program = new Command(); var default_for_cli = { projectName: "default-project", frontendFramework: false, frontendLanguage: false, orm: false, database: false, authentication: false, installDeps: false }; var DEFAULT_OPTIONS = { projectName: "default-project", frontendFramework: "Next.js", frontendLanguage: "TypeScript", orm: "Prisma", database: "PostgreSQL", authentication: "None", installDeps: true }; var cli = async () => { program.argument("[projectName]", "Name of the project").option( "-f, --frontend <framework>", "Frontend framework", default_for_cli.frontendFramework ).option( "-l, --language <language>", "Frontend language", default_for_cli.frontendLanguage ).option( "-o, --orm <orm>", "ORM for database interaction", default_for_cli.orm ).option( "-d, --database <database>", "Database", default_for_cli.database ).option( "-a, --auth <auth>", "Authentication type", default_for_cli.authentication ).option( "-i, --install", "Install Dependencies", default_for_cli.installDeps ).parse(process.argv); const projectNameArg = program.args[0]; const projectName = projectNameArg === "./" || projectNameArg === "." ? path6.basename(process.cwd()) : projectNameArg; const options = program.opts(); const questions = []; if (!projectNameArg) { questions.push({ type: "input", name: "projectName", message: "Enter your project name:", default: DEFAULT_OPTIONS.projectName }); } if (!options.frontend) { questions.push({ type: "list", name: "frontendFramework", message: "Select a frontend framework:", choices: ["React", "Next.js", "Remix"], default: DEFAULT_OPTIONS.frontendFramework }); console.log("options frontend is added on the question array"); } if (!options.language) { questions.push({ type: "list", name: "frontendLanguage", message: "Choose language for frontend:", choices: ["TypeScript", "JavaScript"], default: DEFAULT_OPTIONS.frontendLanguage }); } if (!options.orm) { questions.push({ type: "list", name: "orm", message: "Choose an ORM for database interaction:", choices: ["None", "Prisma", "Drizzle"], default: DEFAULT_OPTIONS.orm }); } if (!options.database) { questions.push({ type: "list", name: "database", message: "Select a database:", choices: ["None", "PostgreSQL", "MySQL"], default: DEFAULT_OPTIONS.database }); } if (!options.auth) { questions.push({ type: "list", name: "authentication", message: "Select authentication type:", choices: ["None", "Hard-coded", "NextAuth", "Lucia Auth"], default: DEFAULT_OPTIONS.authentication }); } if (!options.install) { questions.push({ type: "confirm", name: "installDeps", message: "Do you want to install the dependencies now?", default: DEFAULT_OPTIONS.installDeps }); } const answers = await inquirer.prompt(questions); const finalAnswers = { projectName: projectName ? projectName : answers.projectName, frontendFramework: answers.frontendFramework, frontendLanguage: answers.frontendLanguage, orm: answers.orm, database: answers.database, authentication: answers.authentication, installDeps: answers.installDeps }; return finalAnswers; }; // src/index.ts var setupFunctions = { React: createMERNBoilerplate, "Next.js": createNextTailwindBoilerplate, Remix: async (projectPath, options) => { console.log("Setting up Remix project..."); } }; async function init() { logger.info(title_text_ASCII); const finalAnswers = await cli(); createProject(finalAnswers); } async function createProject(answers) { const projectPath = path7.join(process.cwd(), answers.projectName); await fs5.ensureDir(projectPath); const setupFunction = setupFunctions[answers.frontendFramework]; await setupFunction(projectPath, answers); logger.info(`Successfully created your project at ${projectPath}`); logger.info("Next Steps:"); } init(); //! Log next step is required