@excli/express
Version:
A cli tool for creating Express.js applications, supporting both JavaScript and TypeScript.
414 lines (395 loc) • 11.5 kB
JavaScript
#!/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