create-esyt
Version:
A tool to create a new esyt project with your preferred stack
688 lines (630 loc) • 21.3 kB
JavaScript
#!/usr/bin/env node
import inquirer from "inquirer";
import fs from "fs";
import path from "path";
import { execSync } from "child_process";
function printTitle() {
console.log(`ESYT CLI`);
}
async function run() {
try {
printTitle();
const projectInfo = await inquirer.prompt([
{
type: "input",
name: "projectName",
message: "What will your project be called?",
default: "esyt-app",
},
{
type: "list",
name: "language",
message: "Will you be using JavaScript or TypeScript?",
choices: ["JavaScript", "TypeScript"],
},
{
type: "checkbox",
name: "packages",
message: "Which packages would you like to enable?",
choices: [
"TailwindCSS",
"React Router",
"Framer Motion",
"OGL",
"DotENV",
"Axios",
"Firebase",
"Clerk",
"Appwrite",
"Prisma",
],
default: [],
},
{
type: "confirm",
name: "gitInit",
message: "Initialize a new git repository?",
default: true,
},
{
type: "confirm",
name: "installDeps",
message: "Would you like us to run 'npm i'?",
default: true,
},
{
type: "list",
name: "selectedIDE",
message: "Which IDE would you like to open your project with?",
choices: ["VSCode", "Cursor", "Trae", "None"],
default: "None",
},
]);
if (projectInfo.installDeps) {
const devServerPrompt = await inquirer.prompt([
{
type: "confirm",
name: "runDevServer",
message:
"Would you like to run the development server automatically after setup?",
default: true,
},
]);
projectInfo.runDevServer = devServerPrompt.runDevServer;
} else {
projectInfo.runDevServer = false;
}
console.log(`Good choice! Using ${projectInfo.language}!`);
const projectName = projectInfo.projectName;
const projectPath = path.join(process.cwd(), projectName);
console.log("Running: npm create vite@latest");
let viteCommand = `npm create vite@latest ${projectName} -- --template `;
viteCommand += projectInfo.language === "JavaScript" ? "react" : "react-ts";
execSync(viteCommand, { stdio: "inherit" });
console.log("Vite project created with the specified template.");
process.chdir(projectPath);
console.log(`Changed directory to: ${projectName}`);
try {
if (projectInfo.installDeps) {
console.log("Installing initial dependencies...");
execSync("npm i", { stdio: "inherit" });
} else {
console.log("Skipping initial dependencies installation as requested.");
}
} catch (error) {
console.error("Error during initial setup:", error.message);
}
console.log("\nCleaning up unnecessary files...");
try {
const filesToRemove = [
{
path: path.join(projectPath, "src", "assets"),
isDir: true,
name: "assets folder",
},
{
path: path.join(projectPath, "README.md"),
isDir: false,
name: "README.md file",
},
{
path: path.join(projectPath, "public", "vite.svg"),
isDir: false,
name: "vite.svg from public folder",
},
{
path: path.join(projectPath, "src", "App.css"),
isDir: false,
name: "App.css file",
},
];
for (const file of filesToRemove) {
if (fs.existsSync(file.path)) {
try {
if (file.isDir) {
fs.rmSync(file.path, { recursive: true, force: true });
} else {
fs.unlinkSync(file.path);
}
console.log(`Removed ${file.name}.`);
} catch (error) {
console.warn(`Failed to remove ${file.name}: ${error.message}`);
}
}
}
} catch (error) {
console.error("Error during cleanup:", error.message);
}
console.log("\nCreating components folder and files...");
try {
const componentsDir = path.join(projectPath, "src", "components");
if (!fs.existsSync(componentsDir)) {
fs.mkdirSync(componentsDir);
console.log("Created components directory.");
}
// Create Navbar component
const navbarPath = path.join(
componentsDir,
`Navbar.${projectInfo.language === "JavaScript" ? "jsx" : "tsx"}`
);
fs.writeFileSync(
navbarPath,
`export default function Navbar() {
return (
<>
<h1>Navbar</h1>
</>
)
}`
);
console.log("Created Navbar component.");
// Create Footer component
const footerPath = path.join(
componentsDir,
`Footer.${projectInfo.language === "JavaScript" ? "jsx" : "tsx"}`
);
fs.writeFileSync(
footerPath,
`export default function Footer() {
return (
<>
<h1>Footer</h1>
</>
)
}`
);
console.log("Created Footer component.");
} catch (error) {
console.error("Error creating components:", error.message);
}
console.log("\nUpdating template files...");
try {
const appJsxPath = path.join(
projectPath,
"src",
`App.${projectInfo.language === "JavaScript" ? "jsx" : "tsx"}`
);
if (fs.existsSync(appJsxPath)) {
try {
const appContent = projectInfo.packages.includes("React Router")
? `import { Outlet } from "react-router-dom";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
export default function App() {
return (
<>
<Navbar/>
<Outlet/>
<Footer/>
</>
);
}`
: `import Navbar from "./components/Navbar";
import Home from "./pages/Home";
import Footer from "./components/Footer";
export default function App() {
return (
<>
<Navbar/>
<Footer/>
</>
);
}`;
fs.writeFileSync(appJsxPath, appContent);
console.log("Updated App component with clean template.");
} catch (error) {
console.error(`Failed to update App component: ${error.message}`);
}
} else {
console.warn(`App component file not found at ${appJsxPath}`);
}
const indexHtmlPath = path.join(projectPath, "index.html");
if (fs.existsSync(indexHtmlPath)) {
try {
fs.writeFileSync(
indexHtmlPath,
`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./src/index.css" />
<title>ESYT App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.${
projectInfo.language === "JavaScript" ? "jsx" : "tsx"
}"></script>
</body>
</html>
`
);
console.log("Updated index.html with clean template.");
} catch (error) {
console.error(`Failed to update index.html: ${error.message}`);
}
} else {
console.warn(`index.html not found at ${indexHtmlPath}`);
}
} catch (error) {
console.error("Error updating template files:", error.message);
}
if (projectInfo.installDeps) {
console.log("\nInstalling selected packages...");
try {
if (projectInfo.packages.includes("Clerk")) {
console.log("\nAdding Clerk...");
try {
execSync("npm i @clerk/clerk-react --save", { stdio: "inherit" });
console.log("✅ Clerk installed.");
} catch (error) {
console.error(`❌ Failed to install Clerk: ${error.message}`);
}
}
if (projectInfo.packages.includes("Appwrite")) {
console.log("\nAdding Appwrite...");
try {
execSync("npm i appwrite", { stdio: "inherit" });
console.log("✅ Appwrite installed.");
} catch (error) {
console.error(`❌ Failed to install Appwrite: ${error.message}`);
}
}
if (projectInfo.packages.includes("Prisma")) {
console.log("\nAdding Prisma...");
try {
execSync("npm i prisma --save-dev", { stdio: "inherit" });
execSync("npm i @prisma/client", { stdio: "inherit" });
execSync(
"npx prisma@latest init --datasource-provider postgresql",
{
stdio: "inherit",
}
);
console.log("✅ Prisma initialized.");
} catch (error) {
console.error(`❌ Failed to install Prisma: ${error.message}`);
}
}
if (projectInfo.packages.includes("Framer Motion")) {
console.log("\nAdding Framer Motion...");
try {
execSync("npm i framer-motion", { stdio: "inherit" });
console.log("✅ Framer Motion installed.");
} catch (error) {
console.error(
`❌ Failed to install Framer Motion: ${error.message}`
);
}
}
if (projectInfo.packages.includes("React Router")) {
console.log("\nAdding React Router...");
try {
execSync("npm i react-router-dom", { stdio: "inherit" });
console.log("✅ React Router installed.");
// Update main entry file with React Router configuration
const mainFilePath = path.join(
projectPath,
"src",
`main.${projectInfo.language === "JavaScript" ? "jsx" : "tsx"}`
);
if (fs.existsSync(mainFilePath)) {
fs.writeFileSync(
mainFilePath,
`import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App${
projectInfo.language === "JavaScript" ? ".jsx" : ".tsx"
}";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./pages/Home.jsx"
const router = createBrowserRouter([
{
path: "/",
Component: App,
children: [
{index: true, Component: Home}
]
},
]);
createRoot(document.getElementById("root")).render(
<StrictMode>
<RouterProvider router={router}></RouterProvider>
</StrictMode>
);
`
);
console.log(
"✅ Updated main entry file with React Router configuration."
);
} else {
console.warn(`Main entry file not found at ${mainFilePath}`);
}
} catch (error) {
console.error(
`❌ Failed to install React Router: ${error.message}`
);
}
}
if (projectInfo.packages.includes("OGL")) {
console.log("\nAdding OGL (WebGL Framework)...");
try {
execSync("npm i ogl", { stdio: "inherit" });
console.log("✅ OGL installed.");
} catch (error) {
console.error(`❌ Failed to install OGL: ${error.message}`);
}
}
if (projectInfo.packages.includes("Firebase")) {
console.log("\nAdding Firebase...");
try {
execSync("npm i firebase", { stdio: "inherit" });
// Create firebase directory and config file
const firebaseDir = path.join(projectPath, "src", "firebase");
if (!fs.existsSync(firebaseDir)) {
fs.mkdirSync(firebaseDir, { recursive: true });
}
// Create firebase.config.js
const firebaseConfigPath = path.join(
firebaseDir,
"firebase.config.js"
);
fs.writeFileSync(
firebaseConfigPath,
`import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);`
);
// Create .env file
const envPath = path.join(projectPath, ".env");
fs.writeFileSync(
envPath,
`VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_APP_ID=
VITE_SERVER_URL=`
);
console.log("✅ Firebase installed and configured.");
} catch (error) {
console.error(`❌ Failed to install Firebase: ${error.message}`);
}
}
if (projectInfo.packages.includes("DotENV")) {
console.log("\nAdding DotENV...");
try {
execSync("npm i dotenv", { stdio: "inherit" });
console.log("✅ DotENV installed.");
} catch (error) {
console.error(`❌ Failed to install DotENV: ${error.message}`);
}
}
if (projectInfo.packages.includes("Axios")) {
console.log("\nAdding Axios...");
try {
execSync("npm i axios", { stdio: "inherit" });
console.log("✅ Axios installed.");
} catch (error) {
console.error(`❌ Failed to install Axios: ${error.message}`);
}
}
if (projectInfo.packages.includes("TailwindCSS")) {
console.log("\nAdding Tailwind CSS...");
try {
execSync("npm install tailwindcss @tailwindcss/vite", {
stdio: "inherit",
});
const indexCssPath = path.join(projectPath, "src", "index.css");
if (fs.existsSync(indexCssPath)) {
fs.writeFileSync(indexCssPath, `@import "tailwindcss";\n`);
console.log("✅ Updated index.css with Tailwind directives.");
} else {
console.warn(`index.css not found at ${indexCssPath}`);
}
const tailwindConfigPath = path.join(
projectPath,
"tailwind.config.js"
);
try {
if (!fs.existsSync(tailwindConfigPath)) {
fs.writeFileSync(
tailwindConfigPath,
`/** @type {import('tailwindcss').Config} */\nexport default {\n content: [\n "./index.html",\n "./src/**/*.{js,ts,jsx,tsx}",\n ],\n theme: {\n extend: {},\n },\n plugins: [],\n};\n`
);
console.log(
"✅ Created tailwind.config.js with proper configuration."
);
} else {
console.log(
"✅ tailwind.config.js already exists, skipping creation."
);
}
} catch (error) {
console.error(
`❌ Failed to create tailwind.config.js: ${error.message}`
);
}
const viteConfigPath = path.join(projectPath, "vite.config.js");
if (fs.existsSync(viteConfigPath)) {
fs.writeFileSync(
viteConfigPath,
`import { defineConfig } from "vite";\nimport react from "@vitejs/plugin-react";\nimport tailwindcss from "@tailwindcss/vite";\n\nexport default defineConfig({\n plugins: [react(), tailwindcss()],\n});\n`
);
console.log(
"✅ Updated vite.config.js with TailwindCSS configuration."
);
} else {
console.warn(`vite.config.js not found at ${viteConfigPath}`);
}
console.log("✅ Tailwind CSS installed and configured.");
} catch (error) {
console.error(
`❌ Failed to install Tailwind CSS: ${error.message}`
);
}
}
} catch (error) {
console.error("Error installing packages:", error.message);
}
} else {
console.log(
"\nSkipping package installations as npm install was not selected."
);
console.log(
"You will need to run 'npm install' manually before installing any packages."
);
}
// Create pages directory and Home component
console.log("\nCreating pages directory and components...");
try {
const pagesDir = path.join(projectPath, "src", "pages");
if (!fs.existsSync(pagesDir)) {
fs.mkdirSync(pagesDir);
console.log("Created pages directory.");
}
// Create Home component
const homePath = path.join(
pagesDir,
`Home.${projectInfo.language === "JavaScript" ? "jsx" : "tsx"}`
);
fs.writeFileSync(
homePath,
`export default function Home() {
return (
<>
<h1>Home</h1>
</>
)
}`
);
console.log("Created Home component.");
} catch (error) {
console.error("Error creating pages:", error.message);
}
if (projectInfo.gitInit) {
console.log("\nInitializing Git repository...");
try {
execSync("git init", { stdio: "inherit" });
const gitignorePath = path.join(projectPath, ".gitignore");
if (!fs.existsSync(gitignorePath)) {
fs.writeFileSync(
gitignorePath,
`# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Dependencies
node_modules
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
`
);
console.log("Created .gitignore file.");
}
console.log("✅ Git repository initialized.");
} catch (error) {
console.error(
`❌ Failed to initialize Git repository: ${error.message}`
);
}
} else {
console.log("\nSkipping Git repository initialization as requested.");
}
// Open the project with the selected IDE
try {
if (projectInfo.selectedIDE !== "None") {
console.log(`\nOpening project with ${projectInfo.selectedIDE}...`);
try {
let ideCommand = "";
switch (projectInfo.selectedIDE) {
case "VSCode":
ideCommand = "code .";
break;
case "Cursor":
ideCommand = "cursor .";
break;
case "Trae":
ideCommand = "trae .";
break;
}
if (ideCommand) {
execSync(ideCommand, { stdio: "inherit" });
console.log(`✅ Project opened with ${projectInfo.selectedIDE}.`);
}
} catch (error) {
console.error(
`❌ Failed to open project with ${projectInfo.selectedIDE}: ${error.message}`
);
console.log(
`To open manually, run: 'cd ${projectName}' and then the appropriate IDE command.`
);
}
} else {
console.log("Skipping IDE launch as requested.");
}
} catch (error) {
console.error("Error during IDE launch:", error.message);
}
console.log("\n✨ Project setup complete! ✨");
if (projectInfo.runDevServer) {
console.log("\nStarting development server...");
try {
execSync("npm run dev", { stdio: "inherit" });
} catch (error) {
console.error(
`❌ Failed to start development server: ${error.message}`
);
console.log(
`To start manually, run: 'cd ${projectName}' and 'npm run dev'`
);
}
} else {
console.log(
`\nTo start your project, run: 'cd ${projectName}' and 'npm run dev'`
);
}
} catch (error) {
console.error(
"\n❌ An error occurred during project setup:",
error.message
);
console.error("Please try again or report this issue.");
process.exit(1);
}
}
run().catch((error) => {
console.error("\n❌ An unexpected error occurred:", error.message);
console.error("Please try again or report this issue.");
process.exit(1);
});