UNPKG

@excli/express

Version:

A cli tool for creating Express.js applications, supporting both JavaScript and TypeScript.

414 lines (395 loc) 11.5 kB
#!/usr/bin/env node // src/main.ts import { cwd } from "node:process"; import { fileURLToPath } from "node:url"; import { join as join2, basename, dirname } from "node:path"; import { existsSync, mkdirSync, cpSync, writeFileSync as writeFileSync2 } from "node:fs"; import { spinner, isCancel, multiselect, confirm } from "@clack/prompts"; import { intro, text, select, outro, log } from "@clack/prompts"; // src/scripts.ts import { join } from "node:path"; import { platform } from "node:process"; import { readFileSync, writeFileSync } from "node:fs"; import { spawnSync, spawn } from "node:child_process"; // src/options.ts var tsScripts = { build: "tsc", esbuild: "node esbuild.config.js", "win:dev": "node --watch --env-file=.env dist/main.js", dev: "tsc --watch & node --watch --env-file=.env dist/main.js", start: "node --env-file=.env dist/main.js", "db:start": "docker compose up -d", "db:stop": "docker compose down" }; var jsScripts = { dev: "node --watch --env-file=.env src/main.js", start: "node --env-file=.env src/main.js", "db:start": "docker compose up -d", "db:stop": "docker compose down" }; function prettier() { const prettierrcContent = ` { "semi": true, "singleQuote": false, "tabWidth": 2 } `; const prettierignoreContent = ` build/ dist/ out/ output/ node_modules/ .env .env.*.local .env.development .env.test .env.production .env.local .env.example .vscode/ .idea/ *.log npm-debug.log* yarn-debug.log* yarn-error.log* `; return [ { filename: ".prettierrc", content: prettierrcContent.trim() }, { filename: ".prettierignore", content: prettierignoreContent.trim() } ]; } function env() { const enVariables = `NODE_ENV= PORT= DATABASE_URL= ARGON2_ROUND= JWT_ACCESS_TOKEN_SECRET_KEY= JWT_REFRESH_TOKEN_SECRET_KEY= JWT_ACCESS_TOKEN_EXPIRY_TIME= JWT_REFRESH_TOKEN_EXPIRY_TIME= CLIENT_ORIGIN= `; return [ { variables: enVariables, file: ".env" }, { variables: enVariables, file: ".env.example" } ]; } // src/scripts.ts function hasPkManager(pkgM) { const command = platform !== "win32" ? "which" : "where"; const result = spawnSync(command, [pkgM], { encoding: "utf-8" }); return result.status === 0; } function fireShell(command, args, targetDir = process.cwd()) { return new Promise((resolve, reject) => { const child = spawn(command, args, { cwd: targetDir, stdio: "ignore", shell: true }); child.on("close", (code) => { if (code !== 0) reject(new Error(`Command failed with code ${code}`)); else resolve(""); }); child.on("error", (err) => reject(err)); }); } function modifyPackageJson(targetDir, language) { const fullPath = join(targetDir, "package.json"); const pkg = JSON.parse(readFileSync(fullPath, { encoding: "utf-8" })); pkg.scripts = language === "ts" ? { ...tsScripts } : { ...jsScripts }; pkg.license = "MIT"; pkg.type = "module"; pkg.main = `src/main.js`; writeFileSync(fullPath, JSON.stringify(pkg, null, 2)); } // src/utils.ts import { cancel } from "@clack/prompts"; // src/docker.ts function dockerMongodb(name) { const dockerComposeConfig = ` services: mongodb: image: mongo:latest container_name: ${name} ports: - "27017:27017" restart: always volumes: - mongo:/data/db environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: admin123 networks: default: driver: bridge volumes: mongo: `; return dockerComposeConfig.trim(); } function dockerPostgres(name) { const dockerComposeConfig = ` services: postgres_db: image: postgres:latest container_name: ${name} ports: - "5432:5432" restart: always volumes: - postgres_data:/var/lib/postgresql/data environment: POSTGRES_DB: ${name} POSTGRES_USER: root POSTGRES_PASSWORD: admin123 networks: default: driver: bridge volumes: postgres_data: `; return dockerComposeConfig.trim(); } function dockerMysql(name) { const dockerComposeConfig = ` services: mysql: image: mysql:latest container_name: ${name} ports: - "3306:3306" restart: always volumes: - mysql_data:/data/db environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_USER: root MYSQL_DATABASE: ${name} MYSQL_PASSWORD: admin123 networks: default: driver: bridge volumes: mysql_data: `; return dockerComposeConfig.trim(); } // src/utils.ts var directories = [ "db", "controllers", "routes", "middlewares", "services", "types", "utils", "models" ]; function terminate(message) { cancel(message); process.exit(0); } function sleep(timer = 1500) { return new Promise((r) => setTimeout(r, timer)); } function database(db, name) { switch (db) { case "mongodb": return dockerMongodb(name); case "postgres": return dockerPostgres(name); case "mysql": return dockerMysql(name); default: return null; } } async function packageJsonInit(pkgManager, targetDir, language) { try { let args = []; if (pkgManager === "npm") args = ["init", "-y"]; else if (pkgManager === "pnpm" || pkgManager === "yarn") args = ["init"]; else throw new Error("Unsupported package manager"); await fireShell(pkgManager, args, targetDir); modifyPackageJson(targetDir, language); } catch (err) { console.error(`\u274C ${pkgManager} command failed: ${err}`); } } async function installPackages(pkgManager, targetDir, language, devTools) { const packages = [ "express", "cors", "helmet", "morgan", "compression", "cookie-parser" ]; const devPackages = []; if (devTools.includes("prettier")) devPackages.push("prettier"); if (language === "ts") { devPackages.push( "@types/node", "@types/express", "typescript", "@types/cors", "@types/morgan", "@types/compression", "@types/cookie-parser" ); } const installCmd = pkgManager === "npm" ? "install" : "add"; await fireShell(pkgManager, [installCmd, ...packages], targetDir); if (devPackages.length > 0) { await fireShell(pkgManager, [installCmd, ...devPackages, "-D"], targetDir); } } // src/main.ts var __filename = fileURLToPath(import.meta.url); var __dirname = dirname(__filename); console.clear(); intro( "\u{1F525} Express.js App Generator | \u2728 We will scaffold your app in a few seconds.." ); (async () => { const directory = await text({ message: "What should we name your server directory? \u{1F3AF}", placeholder: "server (Hit Enter for current directory)" }); if (isCancel(directory)) terminate("Process cancelled \u274C"); const rootDir = cwd(); const targetDir = !directory?.trim() ? rootDir : join2(rootDir, directory); const dirName = basename(targetDir) || "container_app"; if (existsSync(targetDir) && directory?.trim()) { return terminate( `${directory} already exists. Please choose a different name \u{1F937}` ); } const language = await select({ message: "Pick your coding Language:", options: [ { label: "TypeScript", value: "ts", hint: "Recommended \u{1F680}" }, { label: "JavaScript", value: "js", hint: "Classic \u{1F4BC}" } ] }); if (isCancel(language)) terminate("Process cancelled \u274C"); const devTools = await multiselect({ message: "\u{1F527} Setting up core development tools...", options: [ { label: "\u2728 Prettier", value: "prettier" }, { label: "\u{1F433} Docker (deployment + database)", value: "docker" }, { label: "\u{1F4DD} Git", value: "git" } ] }); if (isCancel(devTools)) terminate("Process cancelled \u274C"); let db; if (devTools.includes("docker")) { db = await select({ message: "Alright, pick your database:", options: [ { label: "\u{1F42C} MySQL", value: "mysql", hint: "Widely used \u{1F30D}" }, { label: "\u{1F418} PostgreSQL", value: "postgres", hint: "SQL powerhouse \u26A1" }, { label: "\u{1F343} MongoDB", value: "mongodb", hint: "NoSQL flexible \u{1F504}" } ] }); if (isCancel(db)) terminate("Process cancelled \u274C"); } const pkgManager = await select({ message: "Which package manager would you \u2764\uFE0F to use?", options: [ { label: "\u{1F4CB} npm", value: "npm", hint: "Standard choice \u{1F527}" }, { label: "\u{1F9F6} yarn", value: "yarn", hint: "Smooth workflow \u{1F4AB}" }, { label: "\u26A1 pnpm", value: "pnpm", hint: "Lightning fast \u{1F680}" } ] }); if (isCancel(pkgManager)) terminate("Process cancelled \u274C"); if (!hasPkManager(pkgManager)) { terminate( `\u274C ${pkgManager} is not installed on your system! Please install it first.` ); } let builder = false; if (language === "ts") { builder = await confirm({ message: "Do you want esbuild for fast compiling? \u26A1", active: "yes", inactive: "no" }); if (isCancel(builder)) terminate("Process cancelled \u274C"); } const s1 = spinner({ indicator: "dots" }); s1.start("Installation in progress \u2615"); if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true }); const sourceDir = join2(targetDir, "src"); const publicDir = join2(targetDir, "public"); const template = join2(__dirname, "..", "templates", language); if (!existsSync(template)) terminate(`\u274C Template not found at: ${template}`); mkdirSync(publicDir, { recursive: true }); cpSync(template, targetDir, { recursive: true }); for (const { file, variables } of env()) { const fullPath = join2(targetDir, file); writeFileSync2(fullPath, variables); } for (const dir of directories) { if (language !== "ts" && dir === "types") continue; const directoryPath = join2(sourceDir, dir); mkdirSync(directoryPath, { recursive: true }); } if (devTools.includes("prettier")) { for (const { content, filename } of prettier()) { const fullPath = join2(targetDir, filename); writeFileSync2(fullPath, content); } } if (devTools.includes("git")) { await fireShell("npx", ["gitignore", "node"], targetDir); } if (devTools.includes("docker") && db) { const compose = database(db, dirName); const composeFile = join2(targetDir, "compose.yaml"); const DockerFile = join2(targetDir, "Dockerfile"); const dockerignore = join2(__dirname, "..", "templates", ".dockerignore"); writeFileSync2(DockerFile, "", { encoding: "utf-8" }); writeFileSync2(composeFile, compose, { encoding: "utf-8" }); cpSync(dockerignore, join2(targetDir, ".dockerignore")); } await packageJsonInit(pkgManager, targetDir, language); await installPackages(pkgManager, targetDir, language, devTools); if (builder) { const esbuild = join2(__dirname, "..", "templates", "esbuild.config.js"); cpSync(esbuild, join2(targetDir, "esbuild.config.js")); await fireShell(pkgManager, ["install", "-D", "esbuild"], targetDir); await fireShell( pkgManager, ["install", "-D", "esbuild-node-externals"], targetDir ); } await sleep(1e3); s1.stop(`Successfully created project \x1B[32m${dirName}\x1B[0m`); log.success(`Scaffolding project in ${targetDir}...`); outro(`\u{1F680} You're all set! Thanks for using Express App Generator \u{1F64C} GitHub \u2192 https://github.com/pxycknomdictator `); })(); //# sourceMappingURL=main.js.map