create-mytech
Version:
Create Your Own Techstack with basic boilerplate code.
527 lines (507 loc) • 17.5 kB
JavaScript
#!/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