UNPKG

@excli/express

Version:

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

383 lines (365 loc) 10.7 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 figlet from "figlet"; import { spinner, isCancel, multiselect } from "@clack/prompts"; import { text, select, outro, log } from "@clack/prompts"; // src/options.ts var tsScripts = { build: "tsc", dev: "tsx watch --env-file=.env src/main.ts", start: "node --env-file=.env dist/main.js", format: 'prettier --write "**/*.{ts,js,json,md}"', "docker:up": "docker compose up -d", "docker:down": "docker compose down" }; var jsScripts = { dev: "node --watch --env-file=.env src/main.js", start: "node --env-file=.env src/main.js", format: 'prettier --write "**/*.{ts,js,json,md}"', "db:start": "docker compose up -d", "db:stop": "docker compose down" }; function prettier() { const prettierrcContent = ` { "semi": true, "singleQuote": false, "tabWidth": 4 } `; 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= CLIENT_ORIGIN= `; return [ { variables: enVariables, file: ".env" }, { variables: enVariables, file: ".env.example" } ]; } // 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"; 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"]; const devPackages = []; if (devTools.includes("prettier")) devPackages.push("prettier"); if (language === "ts") { devPackages.push( "tsx", "@types/node", "@types/express", "typescript", "@types/cors" ); } 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(); var banner = figlet.textSync("Excli", { font: "Standard", horizontalLayout: "full", verticalLayout: "full" }); console.log(`\x1B[96m ${banner} \x1B[0m`); (async () => { const directory = await text({ message: "What should we name your server directory? \u{1F3AF}", placeholder: "server (Hit Enter for ./)" }); 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.` ); } 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); 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