create-elysiajs
Version:
Scaffolding your Elysia project with the environment with easy!
1,641 lines (1,570 loc) • 53.8 kB
JavaScript
#!/usr/bin/env node
'use strict';
var fs = require('node:fs/promises');
var path = require('node:path');
var enquirer = require('enquirer');
var minimist = require('minimist');
var task = require('tasuku');
var child_process = require('node:child_process');
var node_crypto = require('node:crypto');
var node_util = require('node:util');
function dedent(templ) {
var values = [];
for (var _i = 1; _i < arguments.length; _i++) {
values[_i - 1] = arguments[_i];
}
var strings = Array.from(typeof templ === "string" ? [templ] : templ);
strings[strings.length - 1] = strings[strings.length - 1].replace(/\r?\n([\t ]*)$/, "");
var indentLengths = strings.reduce(function(arr, str) {
var matches = str.match(/\n([\t ]+|(?!\s).)/g);
if (matches) {
return arr.concat(matches.map(function(match) {
var _a, _b;
return (_b = (_a = match.match(/[\t ]/g)) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
}));
}
return arr;
}, []);
if (indentLengths.length) {
var pattern_1 = new RegExp("\n[ ]{" + Math.min.apply(Math, indentLengths) + "}", "g");
strings = strings.map(function(str) {
return str.replace(pattern_1, "\n");
});
}
strings[0] = strings[0].replace(/^\r?\n/, "");
var string = strings[0];
values.forEach(function(value, i) {
var endentations = string.match(/(?:^|\n)( *)$/);
var endentation = endentations ? endentations[1] : "";
var indentedValue = value;
if (typeof value === "string" && value.includes("\n")) {
indentedValue = String(value).split("\n").map(function(str, i2) {
return i2 === 0 ? str : "" + endentation + str;
}).join("\n");
}
string += indentedValue + strings[i + 1];
});
return string;
}
const dependencies = {
"elysia": "^1.2.25",
typescript: "^5.8.2",
"@types/bun": "^1.2.8",
"@biomejs/biome": "^1.9.4",
"eslint": "^9.23.0",
"eslint-plugin-drizzle": "^0.2.3",
"prisma": "^6.5.0",
"@prisma/client": "^6.5.0",
"drizzle-orm": "^0.41.0",
"drizzle-kit": "^0.30.6",
"pg": "^8.14.1",
"@types/pg": "^8.11.11",
postgres: "^3.4.5",
"mysql2": "^3.14.0",
husky: "^9.1.7",
"@elysiajs/bearer": "^1.2.0",
"@elysiajs/cors": "^1.2.0",
"@elysiajs/html": "^1.2.0",
"@kitajs/ts-html-plugin": "^4.1.1",
"@elysiajs/jwt": "^1.2.0",
"@elysiajs/server-timing": "^1.2.1",
"@elysiajs/static": "^1.2.0",
"@elysiajs/swagger": "^1.2.2",
"elysia-autoload": "^1.5.1",
"@bogeychan/elysia-logger": "^0.1.8",
"@antfu/eslint-config": "^4.11.0",
"@gramio/init-data": "^0.0.3",
"elysia-oauth2": "^2.0.0",
"arctic": "^3.6.0",
"env-var": "^7.5.0",
"posthog-node": "^4.11.1",
jobify: "^0.1.6",
"ioredis": "^5.6.0",
"@verrou/core": "^0.5.1",
"@aws-sdk/client-s3": "^3.779.0",
"@elysiajs/eden": "^1.2.0",
"ioredis-mock": "^8.9.0",
"@electric-sql/pglite": "^0.2.17",
gramio: "^0.2.5"
};
const nodeMajorVersion = process?.versions?.node?.split(".")[0];
if (nodeMajorVersion && Number(nodeMajorVersion) < 22)
console.warn(
`Node.js version ${process?.versions?.node} is not recommended for this template. Please upgrade to Node.js 22 or higher.`
);
function detectPackageManager() {
const userAgent = process.env.npm_config_user_agent;
if (!userAgent)
throw new Error(
`Package manager was not detected. Please specify template with "--pm bun"`
);
return userAgent.split(" ")[0].split("/")[0];
}
async function createOrFindDir(path) {
await fs.stat(path).catch(async () => fs.mkdir(path));
}
class Preferences {
projectName = "";
dir = "";
packageManager = "bun";
runtime = "Bun";
linter = "None";
orm = "None";
database = "PostgreSQL";
driver = "None";
git = true;
others = [];
plugins = [];
// integration with create-gramio
isMonorepo = false;
docker = false;
vscode = false;
redis = false;
locks = false;
s3Client = "None";
meta = {
databasePassword: node_crypto.randomBytes(12).toString("hex")
};
noInstall = false;
mockWithPGLite = false;
telegramRelated = false;
}
const exec = node_util.promisify(child_process.exec);
const pmExecuteMap = {
npm: "npx",
bun: "bun x",
yarn: "yarn dlx",
pnpm: "pnpm dlx"
};
const pmRunMap = {
npm: "npm run",
bun: "bun",
yarn: "yarn",
pnpm: "pnpm"
};
const pmLockFilesMap = {
npm: "package.lock.json",
bun: "bun.lock",
yarn: "yarn.lock",
pnpm: "pnpm-lock.yaml"
};
const pmInstallFrozenLockfile = {
npm: "npm ci",
bun: "bun install --frozen-lockfile",
yarn: "yarn install --frozen-lockfile",
pnpm: "pnpm install --frozen-lockfile"
};
const pmInstallFrozenLockfileProduction = {
npm: "npm ci --production",
bun: "bun install --frozen-lockfile --production",
yarn: "yarn install --frozen-lockfile --production",
pnpm: "pnpm install --frozen-lockfile --prod"
};
function getPackageJson({
dir,
projectName,
linter,
packageManager,
orm,
driver,
others,
plugins,
isMonorepo,
locks,
redis,
mockWithPGLite,
telegramRelated,
s3Client
}) {
const sample = {
name: projectName,
type: "module",
scripts: {
dev: packageManager === "bun" ? "bun --watch src/index.ts" : `${pmExecuteMap[packageManager]} tsx watch --env-file .env src/index.ts`,
start: packageManager === "bun" ? "NODE_ENV=production bun run ./src/index.ts" : `NODE_ENV=production ${pmExecuteMap[packageManager]} tsx --env-file=.env --env-file=.env.production src/index.ts`
},
dependencies: {
elysia: dependencies.elysia,
"env-var": dependencies["env-var"]
},
devDependencies: {
typescript: dependencies.typescript
}
};
sample.devDependencies["@types/bun"] = dependencies["@types/bun"];
if (linter === "Biome") {
sample.scripts.lint = `${pmExecuteMap[packageManager]} @biomejs/biome check src`;
sample.scripts["lint:fix"] = `${pmRunMap[packageManager]} lint --write`;
sample.devDependencies["@biomejs/biome"] = dependencies["@biomejs/biome"];
}
if (linter === "ESLint") {
sample.scripts.lint = `${pmExecuteMap[packageManager]} eslint`;
sample.scripts["lint:fix"] = `${pmExecuteMap[packageManager]} eslint --fix`;
sample.devDependencies.eslint = dependencies.eslint;
sample.devDependencies["@antfu/eslint-config"] = dependencies["@antfu/eslint-config"];
if (orm === "Drizzle")
sample.devDependencies["eslint-plugin-drizzle"] = dependencies["eslint-plugin-drizzle"];
}
if (orm === "Prisma") {
sample.devDependencies.prisma = dependencies.prisma;
sample.dependencies["@prisma/client"] = dependencies["@prisma/client"];
}
if (orm === "Drizzle") {
sample.dependencies["drizzle-orm"] = dependencies["drizzle-orm"];
sample.devDependencies["drizzle-kit"] = dependencies["drizzle-kit"];
if (driver === "node-postgres") {
sample.dependencies.pg = dependencies.pg;
sample.devDependencies["@types/pg"] = dependencies["@types/pg"];
}
if (driver === "Postgres.JS") {
sample.dependencies.postgres = dependencies.postgres;
}
if (driver === "MySQL 2") {
sample.dependencies.mysql2 = dependencies.mysql2;
}
sample.scripts.generate = `${pmExecuteMap[packageManager]} drizzle-kit generate`;
sample.scripts.push = `${pmExecuteMap[packageManager]} drizzle-kit push`;
sample.scripts.migrate = `${pmExecuteMap[packageManager]} drizzle-kit migrate`;
sample.scripts.studio = `${pmExecuteMap[packageManager]} drizzle-kit studio`;
}
if (others.includes("Husky")) {
sample.devDependencies.husky = dependencies.husky;
sample.scripts.prepare = "husky";
}
if (plugins.includes("Bearer"))
sample.dependencies["@elysiajs/bearer"] = dependencies["@elysiajs/bearer"];
if (plugins.includes("CORS"))
sample.dependencies["@elysiajs/cors"] = dependencies["@elysiajs/cors"];
if (plugins.includes("HTML/JSX")) {
sample.dependencies["@elysiajs/html"] = dependencies["@elysiajs/html"];
sample.dependencies["@kitajs/ts-html-plugin"] = dependencies["@kitajs/ts-html-plugin"];
}
if (plugins.includes("JWT"))
sample.dependencies["@elysiajs/jwt"] = dependencies["@elysiajs/jwt"];
if (plugins.includes("Server Timing"))
sample.dependencies["@elysiajs/server-timing"] = dependencies["@elysiajs/server-timing"];
if (plugins.includes("Static"))
sample.dependencies["@elysiajs/static"] = dependencies["@elysiajs/static"];
if (plugins.includes("Swagger"))
sample.dependencies["@elysiajs/swagger"] = dependencies["@elysiajs/swagger"];
if (plugins.includes("Autoload"))
sample.dependencies["elysia-autoload"] = dependencies["elysia-autoload"];
if (plugins.includes("Logger"))
sample.dependencies["@bogeychan/elysia-logger"] = dependencies["@bogeychan/elysia-logger"];
if (plugins.includes("Oauth 2.0")) {
sample.dependencies.arctic = dependencies.arctic;
sample.dependencies["elysia-oauth2"] = dependencies["elysia-oauth2"];
}
if (redis) {
sample.dependencies.ioredis = dependencies.ioredis;
if (mockWithPGLite)
sample.devDependencies["ioredis-mock"] = dependencies["ioredis-mock"];
}
if (others.includes("Jobify")) {
sample.dependencies.jobify = dependencies.jobify;
}
if (others.includes("Posthog")) {
sample.dependencies["posthog-node"] = dependencies["posthog-node"];
}
if (locks) {
sample.dependencies["@verrou/core"] = dependencies["@verrou/core"];
}
if (isMonorepo)
sample.dependencies["@gramio/init-data"] = dependencies["@gramio/init-data"];
if (others.includes("S3") && s3Client === "@aws-sdk/client-s3") {
sample.dependencies["@aws-sdk/client-s3"] = dependencies["@aws-sdk/client-s3"];
}
if (mockWithPGLite) {
sample.devDependencies["@electric-sql/pglite"] = dependencies["@electric-sql/pglite"];
sample.devDependencies["@elysiajs/eden"] = dependencies["@elysiajs/eden"];
}
if (telegramRelated && !isMonorepo) {
sample.dependencies.gramio = dependencies.gramio;
sample.dependencies["@gramio/init-data"] = dependencies["@gramio/init-data"];
}
return JSON.stringify(sample, null, 2);
}
function getElysiaIndex({
orm,
driver,
plugins,
telegramRelated,
isMonorepo
}) {
const elysiaPlugins = [];
const elysiaImports = [
`import { Elysia } from "elysia"`,
`import { config } from "./config.ts"`
];
if (plugins.includes("Logger")) {
elysiaImports.push(`import { logger } from "@bogeychan/elysia-logger"`);
elysiaPlugins.push(".use(logger())");
}
if (plugins.includes("Swagger")) {
elysiaImports.push(`import { swagger } from "@elysiajs/swagger"`);
elysiaPlugins.push(".use(swagger())");
}
if (plugins.includes("Oauth 2.0")) {
elysiaImports.push(`import { oauth2 } from "elysia-oauth2"`);
elysiaPlugins.push(".use(oauth2({}))");
}
if (plugins.includes("Bearer")) {
elysiaImports.push(`import { bearer } from "@elysiajs/bearer"`);
elysiaPlugins.push(".use(bearer())");
}
if (plugins.includes("CORS")) {
elysiaImports.push(`import { cors } from "@elysiajs/cors"`);
elysiaPlugins.push(".use(cors())");
}
if (plugins.includes("HTML/JSX")) {
elysiaImports.push(`import { html } from "@elysiajs/html"`);
elysiaPlugins.push(".use(html())");
}
if (plugins.includes("JWT")) {
elysiaImports.push(`import { jwt } from "@elysiajs/jwt"`);
elysiaPlugins.push(".use(jwt({ secret: config.JWT_SECRET }))");
}
if (plugins.includes("Server Timing")) {
elysiaImports.push(
`import { serverTiming } from "@elysiajs/server-timing"`
);
elysiaPlugins.push(".use(serverTiming())");
}
if (plugins.includes("Static")) {
elysiaImports.push(`import { staticPlugin } from "@elysiajs/static"`);
elysiaPlugins.push(".use(staticPlugin())");
}
if (plugins.includes("Autoload")) {
elysiaImports.push(`import { autoload } from "elysia-autoload"`);
elysiaPlugins.push(".use(autoload())");
}
elysiaPlugins.push(`.get("/", "Hello World")`);
if (telegramRelated && !isMonorepo) {
elysiaImports.push(`import { bot } from "./bot.ts"`);
elysiaImports.push(`import { webhookHandler } from "./services/auth.ts"`);
elysiaPlugins.push(
`.post(\`/\${config.BOT_TOKEN}\`, webhookHandler(bot, "elysia"), {
detail: {
hide: true,
},
})`
);
}
return [
...elysiaImports,
"",
"export const app = new Elysia()",
...elysiaPlugins,
plugins.includes("Autoload") ? "\nexport type ElysiaApp = typeof app" : ""
].join("\n");
}
function getInstallCommands({
linter,
orm,
database,
git,
others
}) {
const commands = [];
if (git) commands.push("git init");
commands.push("bun install");
if (others.includes("Husky") && linter !== "None")
commands.push(`echo "bun lint:fix" > .husky/pre-commit`);
if (orm === "Prisma")
commands.push(
`bunx prisma init --datasource-provider ${database.toLowerCase()}`
);
if (linter === "Biome") commands.push("bunx @biomejs/biome init");
if (linter !== "None") commands.push("bun lint:fix");
return commands;
}
const driverNamesToDrizzle = {
"node-postgres": "node-postgres",
"Bun.sql": "bun-sql",
"Postgres.JS": "postgres-js",
"MySQL 2": "mysql2",
"Bun SQLite": "bun-sqlite",
None: ""
};
const driverNames = {
"node-postgres": "pg",
"Bun.sql": "??",
"Postgres.JS": "postgres",
"MySQL 2": "mysql2",
"Bun SQLite": "bun:sqlite",
None: ""
};
function getDBIndex({ orm, driver, packageManager }) {
if (orm === "Prisma")
return [
`import { PrismaClient } from "@prisma/client"`,
"",
"export const prisma = new PrismaClient()",
"",
`export * from "@prisma/client"`
].join("\n");
if (driver === "node-postgres")
return [
`import { drizzle } from "drizzle-orm/node-postgres"`,
`import { Client } from "pg"`,
`import { config } from "../config.ts"`,
"",
"export const client = new Client({",
" connectionString: config.DATABASE_URL,",
"})",
"",
"export const db = drizzle({",
" client,",
' casing: "snake_case",',
"})"
].join("\n");
if (driver === "Postgres.JS")
return [
`import { drizzle } from "drizzle-orm/postgres-js"`,
`import postgres from "postgres"`,
`import { config } from "../config.ts"`,
"",
"const client = postgres(config.DATABASE_URL)",
"export const db = drizzle({",
" client,",
' casing: "snake_case",',
"})"
].join("\n");
if (driver === "Bun.sql")
return [
`import { drizzle } from "drizzle-orm/bun-sql"`,
`import { config } from "../config.ts"`,
`import { SQL } from "bun"`,
"",
"export const sql = new SQL(config.DATABASE_URL)",
"",
"export const db = drizzle({",
" client: sql,",
' casing: "snake_case",',
"})"
].join("\n");
if (driver === "MySQL 2")
return [
`import { drizzle } from "drizzle-orm/mysql2"`,
`import mysql from "mysql2/promise"`,
`import { config } from "../config.ts"`,
"",
"export const connection = await mysql.createConnection(config.DATABASE_URL)",
`console.log("\u{1F5C4}\uFE0F Database was connected!")`,
"",
"export const db = drizzle({",
" client: connection,",
' casing: "snake_case",',
"})"
].join("\n");
if (driver === "Bun SQLite" && packageManager === "bun")
return [
`import { drizzle } from "drizzle-orm/bun-sqlite"`,
`import { Database } from "bun:sqlite";`,
"",
`export const sqlite = new Database("sqlite.db")`,
"export const db = drizzle({",
" client: sqlite,",
' casing: "snake_case",',
"})"
].join("\n");
return [
`import { drizzle } from "drizzle-orm/better-sqlite3`,
`import { Database } from "better-sqlite3";`,
"",
`export const sqlite = new Database("sqlite.db")`,
"export const db = drizzle({",
" client: sqlite,",
' casing: "snake_case",',
"})"
].join("\n");
}
function getDrizzleConfig({ database }) {
return [
`import type { Config } from "drizzle-kit"`,
`import env from "env-var"`,
"",
'const DATABASE_URL = env.get("DATABASE_URL").required().asString()',
"",
"export default {",
` schema: "./src/db/schema.ts",`,
` out: "./drizzle",`,
` dialect: "${database.toLowerCase()}",`,
` casing: "snake_case",`,
" dbCredentials: {",
" url: DATABASE_URL",
" }",
"} satisfies Config"
].join("\n");
}
function getTSConfig({ plugins }) {
return JSON.stringify(
{
compilerOptions: {
lib: ["ESNext"],
module: "NodeNext",
target: "ESNext",
moduleResolution: "NodeNext",
esModuleInterop: true,
strict: true,
skipLibCheck: true,
allowSyntheticDefaultImports: true,
noEmit: true,
allowImportingTsExtensions: true,
noUncheckedIndexedAccess: true,
...plugins.includes("HTML/JSX") ? {
jsx: "react",
jsxFactory: "Html.createElement",
jsxFragmentFactory: "Html.Fragment",
plugins: [{ name: "@kitajs/ts-html-plugin" }]
} : {}
},
include: ["src"]
},
null,
2
);
}
const connectionURLExamples = {
PostgreSQL: "postgresql://root:mypassword@localhost:5432/mydb",
MySQL: "mysql://root:mypassword@localhost:3306/mydb",
SQLServer: "sqlserver://localhost:1433;database=mydb;user=root;password=mypassword;",
CockroachDB: "postgresql://root:mypassword@localhost:26257/mydb?schema=public",
MongoDB: "mongodb+srv://root:mypassword@cluster0.ab1cd.mongodb.net/mydb?retryWrites=true&w=majority",
SQLite: "file:./sqlite.db"
};
const composeServiceNames = {
PostgreSQL: "postgres",
MySQL: "localhost",
SQLServer: "localhost",
CockroachDB: "localhost",
MongoDB: "localhost",
SQLite: "file:./sqlite.db"
};
function getEnvFile({
database,
orm,
plugins,
projectName,
redis,
meta,
telegramRelated
}, isComposed = false) {
const envs = [];
if (orm !== "None") {
let url = connectionURLExamples[database].replace("mydb", projectName).replace("root", projectName).replace("mypassword", meta.databasePassword);
if (isComposed)
url = url.replace("localhost", composeServiceNames[database]);
envs.push(`DATABASE_URL="${url}"`);
}
if (telegramRelated) {
envs.push(`BOT_TOKEN=""`);
}
if (isComposed && redis) envs.push("REDIS_HOST=redis");
if (plugins.includes("JWT"))
envs.push(`JWT_SECRET="${node_crypto.randomBytes(12).toString("hex")}"`);
envs.push("PORT=3000");
return envs.join("\n");
}
function getConfigFile({
orm,
redis,
others,
plugins,
locks,
telegramRelated
}) {
const envs = [];
envs.push(`PORT: env.get("PORT").default(3000).asPortNumber()`);
envs.push(
`API_URL: env.get("API_URL").default(\`https://\${env.get("PUBLIC_DOMAIN").asString()}\`).asString()`
);
if (telegramRelated) {
envs.push(`BOT_TOKEN: env.get("BOT_TOKEN").required().asString()`);
}
if (orm !== "None")
envs.push(`DATABASE_URL: env.get("DATABASE_URL").required().asString()`);
if (redis) {
envs.push(
`REDIS_HOST: env.get("REDIS_HOST").default("localhost").asString()`
);
}
if (others.includes("Posthog")) {
envs.push(
`POSTHOG_API_KEY: env.get("POSTHOG_API_KEY").default("it's a secret").asString()`
);
envs.push(
`POSTHOG_HOST: env.get("POSTHOG_HOST").default("localhost").asString()`
);
}
if (others.includes("S3")) {
envs.push(
`S3_ENDPOINT: env.get("S3_ENDPOINT").default("localhost").asString()`
);
envs.push(
`S3_ACCESS_KEY_ID: env.get("S3_ACCESS_KEY_ID").default("minio").asString()`
);
envs.push(
`S3_SECRET_ACCESS_KEY: env.get("S3_SECRET_ACCESS_KEY").default("minio").asString()`
);
}
if (locks) {
const stores = ["memory"];
if (redis) stores.push("redis");
envs.push(
`LOCK_STORE: env.get("LOCK_STORE").default("memory").asEnum(${JSON.stringify(stores)})`
);
}
if (plugins.includes("JWT"))
envs.push(`JWT_SECRET: env.get("JWT_SECRET").required().asString()`);
return dedent`
import env from "env-var";
export const config = {
NODE_ENV: env
.get("NODE_ENV")
.default("development")
.asEnum(["production", "test", "development"]),
${envs.join(",\n")}
}`;
}
const links = {
Bun: "[Bun](https://bun.sh/)",
ElysiaJS: "[ElysiaJS](https://elysiajs.com/)",
ESLint: "[ESLint](https://eslint.org/)",
Biome: "[Biome](https://biomejs.dev/)",
Prisma: "[Prisma](https://www.prisma.io/)",
Drizzle: "[Drizzle](https://orm.drizzle.team/)",
CORS: "[CORS](https://elysiajs.com/plugins/cors.html)",
Swagger: "[Swagger](https://elysiajs.com/plugins/swagger.html)",
JWT: "[JWT](https://elysiajs.com/plugins/jwt.html)",
Autoload: "[Autoload](https://github.com/kravetsone/elysia-autoload)",
"Oauth 2.0": "[Oauth 2.0](https://github.com/kravetsone/elysia-oauth2)",
Logger: "[Logger](https://github.com/bogeychan/elysia-logger)",
"HTML/JSX": "[HTML/JSX](https://elysiajs.com/plugins/html.html)",
Static: "[Static](https://elysiajs.com/plugins/static.html)",
Bearer: "[Bearer](https://elysiajs.com/plugins/bearer.html)",
"Server Timing": "[Server Timing](https://elysiajs.com/plugins/server-timing.html)",
Husky: "[Husky](https://typicode.github.io/husky/)",
PostgreSQL: "[PostgreSQL](https://www.postgresql.org/)",
MySQL: "[MySQL](https://www.mysql.com/)",
MongoDB: "[MongoDB](https://www.mongodb.com/)",
SQLite: "[SQLite](https://sqlite.org/)",
SQLServer: "[SQLServer](https://www.microsoft.com/sql-server)",
CockroachDB: "[CockroachDB](https://www.cockroachlabs.com/)",
Jobify: "[Jobify](https://github.com/kravetsone/jobify)",
Docker: "[Docker](https://www.docker.com/)",
Posthog: "[Posthog](https://posthog.com/docs/libraries/node)",
PGLite: "[PGLite](https://pglite.dev/)",
S3: "[Minio](https://github.com/minio/minio)",
Redis: "[Redis](https://redis.io/) + [ioredis](https://github.com/redis/ioredis)",
IoRedisMock: "[ioredis-mock](https://www.npmjs.com/package/ioredis-mock)"
};
const TESTS_REPO_LINK = "[tests](tree/main/tests)";
function getReadme({
dir,
linter,
orm,
database,
plugins,
others,
docker,
mockWithPGLite,
redis
}) {
const stack = [];
stack.push(`- Web framework - ${links.ElysiaJS}`);
if (linter !== "None") stack.push(`- Linter - ${links[linter]}`);
if (orm !== "None")
stack.push(
`- ORM - ${links[orm]} (${links[database]})${mockWithPGLite ? ` (mocked with ${links.PGLite} in ${TESTS_REPO_LINK})` : ""}`
);
if (plugins.length)
stack.push(`- Elysia plugins - ${plugins.map((x) => links[x]).join(", ")}`);
if (others.length)
stack.push(
`- Others tools - ${[
docker ? links.Docker : void 0,
redis ? mockWithPGLite ? `${links.Redis} + ${links.IoRedisMock} in tests` : links.Redis : void 0,
...others.map((x) => links[x])
].filter(Boolean).join(", ")}`
);
const instruction = [];
instruction.push("## Development\n");
if (docker) {
instruction.push(
"Start development services (DB, Redis etc):\n",
"```bash",
"docker compose -f docker-compose.dev.yml up",
"```\n"
);
}
instruction.push("Start the project:\n", "```bash", "bun dev", "```\n");
if (orm === "Drizzle") {
instruction.push(
"## Migrations\n",
"Push schema to Database:\n",
"```bash",
"bunx drizzle-kit push",
"```",
"Generate new migration:\n",
"```bash",
"bunx drizzle-kit generate",
"```",
"Apply migrations:\n",
"```bash",
"bunx drizzle-kit migrate",
"```\n"
);
}
if (orm === "Prisma") {
instruction.push(
"## Migrations\n",
"Generate new migration:\n",
"```bash",
"bunx prisma migrate dev",
"```",
"Apply migrations:\n",
"```bash",
"bunx prisma migrate deploy",
"```\n"
);
}
if (mockWithPGLite) {
instruction.push(
"## Tests\n",
`Tests are written with ${links.Bun}:test.
`,
"Mocks:\n",
`- Postgres usage is mocked with ${links.PGLite}`,
`- Redis usage is mocked with ${links.IoRedisMock}`,
"\n",
"```bash",
"bun test",
"```\n"
);
}
instruction.push("## Production\n");
if (docker) {
instruction.push(
"Run project in `production` mode:\n",
"```bash",
"docker compose up -d",
"```"
);
} else
instruction.push(
"Run project in `production` mode:\n",
"```bash",
"bun start",
"```"
);
return [
`# ${dir}`,
"",
"This template autogenerated by [create-elysiajs](https://github.com/kravetsone/create-elysiajs)",
"",
"### Stack",
...stack,
"",
// "### Instructions",
...instruction
].join("\n");
}
function generateEslintConfig({ orm }) {
return [
`import antfu from "@antfu/eslint-config"`,
orm === "Drizzle" && `import drizzle from "eslint-plugin-drizzle";`,
`
export default antfu(
{
stylistic: {
indent: 2,
quotes: "double",
},
},
{
files: ["**/*.js", "**/*.ts"],
rules: {
"node/prefer-global/process": "off",
"no-console": "off",
"antfu/no-top-level-await": "off",
},`,
orm === "Drizzle" && `plugins: {
drizzle,
},`,
`
},
);
`
].filter(Boolean).join("\n");
}
const dbExportedMap = {
Prisma: "prisma",
Drizzle: "client"
};
function getIndex({
others,
orm,
driver,
telegramRelated,
isMonorepo
}) {
const isShouldConnectToDB = orm !== "None" && driver !== "Postgres.JS" && driver !== "MySQL 2" && driver !== "Bun SQLite" && driver !== "Bun.sql";
const gracefulShutdownTasks = [];
const imports = [
// `import { bot } from "./bot.ts"`,
`import { config } from "./config.ts"`
];
const startUpTasks = [];
imports.push(`import { app } from "./server.ts"`);
gracefulShutdownTasks.push("await app.stop()");
if (others.includes("Posthog")) {
imports.push(`import { posthog } from "./services/posthog.ts"`);
gracefulShutdownTasks.push("await posthog.shutdown()");
}
if (isShouldConnectToDB) {
imports.push(`import { ${dbExportedMap[orm]} } from "./db/index.ts"`);
startUpTasks.push(dedent`
${orm === "Prisma" ? "await prisma.$connect()" : "await client.connect()"}
console.log("🗄️ Database was connected!")`);
}
startUpTasks.push(
/*ts*/
`
app.listen(config.PORT, () => console.log(\`\u{1F98A} Server started at \${app.server?.url.origin}\`))
`
);
if (telegramRelated && !isMonorepo) {
imports.push(`import { bot } from "./bot.ts"`);
startUpTasks.push(dedent`
if (config.NODE_ENV === "production")
await bot.start({
webhook: {
url: \`\${config.API_URL}/\${config.BOT_TOKEN}\`,
},
});
else await bot.start();`);
}
return dedent`
${imports.join("\n")}
const signals = ["SIGINT", "SIGTERM"];
for (const signal of signals) {
process.on(signal, async () => {
console.log(\`Received \${signal}. Initiating graceful shutdown...\`);
${gracefulShutdownTasks.join("\n")}
process.exit(0);
})
}
process.on("uncaughtException", (error) => {
console.error(error);
})
process.on("unhandledRejection", (error) => {
console.error(error);
})
${startUpTasks.join("\n")}`;
}
function getBotFile() {
return dedent`
import { Bot } from "gramio";
import { config } from "./config.ts";
export const bot = new Bot(config.BOT_TOKEN)
.onStart(({ info }) => console.log(\`✨ Bot \${info.username} was started!\`))`;
}
const ormDockerCopy = {
Prisma: "COPY --from=prerelease /usr/src/app/prisma ./prisma",
Drizzle: dedent`
COPY --from=prerelease /usr/src/app/drizzle ./drizzle
COPY --from=prerelease /usr/src/app/drizzle.config.ts .`
};
function getDockerfile({ packageManager, orm }) {
if (packageManager === "bun")
return dedent`
# use the official Bun image
# see all versions at https://hub.docker.com/r/oven/bun/tags
FROM oven/bun:${process.versions.bun ?? "1.2.5"} AS base
WORKDIR /usr/src/app
# install dependencies into temp directory
# this will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
RUN cd /temp/dev && bun install --frozen-lockfile
# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
RUN cd /temp/prod && bun install --frozen-lockfile --production
# copy node_modules from temp directory
# then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
ENV NODE_ENV=production
RUN ${pmExecuteMap[packageManager]} tsc --noEmit
# copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/.env .
COPY --from=prerelease /usr/src/app/.env.production .
COPY --from=prerelease /usr/src/app/${pmLockFilesMap[packageManager]} .
RUN mkdir -p /usr/src/app/src
COPY --from=prerelease /usr/src/app/src ./src
COPY --from=prerelease /usr/src/app/package.json .
COPY --from=prerelease /usr/src/app/tsconfig.json .
${orm !== "None" ? ormDockerCopy[orm] : ""}
ENTRYPOINT [ "bun", "start" ]`;
return dedent`
# Use the official Node.js 22 image.
# See https://hub.docker.com/_/node for more information.
FROM node:${process?.versions?.node ?? "22.12"} AS base
# Create app directory
WORKDIR /usr/src/app
${packageManager !== "npm" ? "npm install ${packageManager} -g" : ""}
# Install dependencies into temp directory
# This will cache them and speed up future builds
FROM base AS install
RUN mkdir -p /temp/dev
COPY package.json ${pmLockFilesMap[packageManager]} /temp/dev/
RUN cd /temp/dev && ${pmInstallFrozenLockfile[packageManager]}
# Install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json ${pmLockFilesMap[packageManager]} /temp/prod/
RUN cd /temp/prod && ${pmInstallFrozenLockfileProduction[packageManager]}
# Copy node_modules from temp directory
# Then copy all (non-ignored) project files into the image
FROM base AS prerelease
COPY --from=install /temp/dev/node_modules node_modules
COPY . .
ENV NODE_ENV=production
RUN ${pmExecuteMap[packageManager]} tsc --noEmit
# Copy production dependencies and source code into final image
FROM base AS release
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=prerelease /usr/src/app/.env .
COPY --from=prerelease /usr/src/app/.env.production .
RUN mkdir -p /usr/src/app/src
COPY --from=prerelease /usr/src/app/src ./src
COPY --from=prerelease /usr/src/app/${pmLockFilesMap[packageManager]} .
COPY --from=prerelease /usr/src/app/package.json .
COPY --from=prerelease /usr/src/app/tsconfig.json .
${orm !== "None" ? ormDockerCopy[orm] : ""}
# TODO:// should be downloaded not at ENTRYPOINT
ENTRYPOINT [ "${pmRunMap[packageManager]}", "start" ]`;
}
function getDockerCompose({
database,
redis,
projectName,
meta,
others
}) {
const volumes = [];
if (database === "PostgreSQL") volumes.push("postgres_data:");
if (redis) volumes.push("redis_data:");
if (others.includes("S3")) volumes.push("minio_data:");
const services = [
/* yaml */
`bot:
container_name: ${projectName}-bot
restart: unless-stopped
build:
context: .
dockerfile: Dockerfile
environment:
- NODE_ENV=production`,
database === "PostgreSQL" ? (
/* yaml */
`postgres:
container_name: ${projectName}-postgres
image: postgres:latest
restart: unless-stopped
environment:
- POSTGRES_USER=${projectName}
- POSTGRES_PASSWORD=${meta.databasePassword}
- POSTGRES_DB=${projectName}
volumes:
- postgres_data:/var/lib/postgresql/data`
) : "",
redis ? (
/* yaml */
`redis:
container_name: ${projectName}-redis
image: redis:latest
command: [ "redis-server", "--maxmemory-policy", "noeviction" ]
restart: unless-stopped
volumes:
- redis_data:/data`
) : "",
others.includes("S3") ? (
/* yaml */
`minio:
container_name: ${projectName}-minio
image: minio/minio:latest
command: [ "minio", "server", "/data", "--console-address", ":9001" ]
restart: unless-stopped
environment:
- MINIO_ACCESS_KEY=${projectName}
- MINIO_SECRET_KEY=${meta.databasePassword}
ports:
- 9000:9000
- 9001:9001
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 5`
) : ""
];
return dedent`
services:
${services.filter(Boolean).join("\n")}
volumes:
${volumes.join("\n")}
networks:
default: {}
`;
}
function getDevelopmentDockerCompose({
database,
redis,
projectName,
meta,
others
}) {
const volumes = [];
if (database === "PostgreSQL") volumes.push("postgres_data:");
if (redis) volumes.push("redis_data:");
if (others.includes("S3")) volumes.push("minio_data:");
const services = [
database === "PostgreSQL" ? (
/* yaml */
`postgres:
container_name: ${projectName}-postgres
image: postgres:latest
restart: unless-stopped
environment:
- POSTGRES_USER=${projectName}
- POSTGRES_PASSWORD=${meta.databasePassword}
- POSTGRES_DB=${projectName}
ports:
- 5432:5432
volumes:
- postgres_data:/var/lib/postgresql/data`
) : "",
redis ? (
/* yaml */
`redis:
container_name: ${projectName}-redis
image: redis:latest
command: [ "redis-server", "--maxmemory-policy", "noeviction" ]
restart: unless-stopped
ports:
- 6379:6379
volumes:
- redis_data:/data`
) : "",
others.includes("S3") ? (
/* yaml */
`minio:
container_name: ${projectName}-minio
image: minio/minio:latest
command: [ "minio", "server", "/data", "--console-address", ":9001" ]
restart: unless-stopped
environment:
- MINIO_ACCESS_KEY=${projectName}
- MINIO_SECRET_KEY=${meta.databasePassword}
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 5`
) : ""
];
return dedent`
services:
${services.filter(Boolean).join("\n")}
volumes:
${volumes.join("\n")}
networks:
default: {}
`;
}
function getAuthPlugin() {
return dedent`
import { validateAndParseInitData, signInitData, getBotTokenSecretKey } from "@gramio/init-data";
import { Elysia, t } from "elysia";
import { config } from "../config.ts";
const secretKey = getBotTokenSecretKey(config.BOT_TOKEN);
export const authElysia = new Elysia({
name: "auth",
})
.guard({
headers: t.Object({
"x-init-data": t.String({
examples: [
signInitData(
{
user: {
id: 1,
first_name: "durov",
username: "durov",
},
},
secretKey
),
],
}),
}),
response: {
401: t.Literal("UNAUTHORIZED"),
},
})
.resolve(({ headers, error }) => {
const result = validateAndParseInitData(
headers["x-init-data"],
secretKey
);
if (!result || !result.user)
return error("Unauthorized", "UNAUTHORIZED");
return {
tgId: result.user.id,
user: result.user,
};
})
.as("plugin");`;
}
function getJobifyFile() {
return dedent`
import { initJobify } from "jobify"
import { redis } from "./redis.ts"
export const defineJob = initJobify(redis);
`;
}
function getLocksFile({ redis }) {
const imports = [];
const stores = [];
stores.push("memory: { driver: memoryStore() }");
imports.push(`import { memoryStore } from '@verrou/core/drivers/memory'`);
if (redis) {
stores.push("redis: { driver: redisStore({ connection: redis }) },");
imports.push(`import { redisStore } from '@verrou/core/drivers/redis'`);
imports.push(`import { redis } from './redis.ts'`);
}
return dedent`
import { Verrou } from "@verrou/core"
import { config } from "../config.ts"
${imports.join("\n")}
export const verrou = new Verrou({
default: config.LOCK_STORE,
stores: {
${stores.join(",\n")}
}
})
`;
}
function getPosthogIndex() {
return dedent`
import { PostHog } from "posthog-node";
import { config } from "../config.ts";
export const posthog = new PostHog(config.POSTHOG_API_KEY, {
host: config.POSTHOG_HOST,
disabled: config.NODE_ENV !== "production",
});
posthog.on("error", (err) => {
console.error("PostHog had an error!", err)
})
`;
}
function getRedisFile() {
return dedent`
import { Redis } from "ioredis";
import { config } from "../config.ts"
export const redis = new Redis({
host: config.REDIS_HOST,
// for bullmq
maxRetriesPerRequest: null,
})
`;
}
function getS3ServiceFile({ s3Client }) {
if (s3Client === "Bun.S3Client") {
return dedent`
import { S3Client } from "bun";
import { config } from "../config.ts";
export const s3 = new S3Client({
endpoint: config.S3_ENDPOINT,
accessKeyId: config.S3_ACCESS_KEY_ID,
secretAccessKey: config.S3_SECRET_ACCESS_KEY,
});
`;
}
if (s3Client === "@aws-sdk/client-s3") {
return dedent`
import { S3Client } from "@aws-sdk/client-s3";
import { config } from "../config.ts";
export const s3 = new S3Client({
endpoint: config.S3_ENDPOINT,
region: "minio",
credentials: {
accessKeyId: config.S3_ACCESS_KEY_ID,
secretAccessKey: config.S3_SECRET_ACCESS_KEY,
},
});
`;
}
return "";
}
function getPreloadFile({ redis, driver }) {
const imports = [];
const mocks = [];
if (redis) {
imports.push('import redis from "ioredis-mock"');
mocks.push(
"mock.module('ioredis', () => ({ Redis: redis, default: redis }))"
);
}
return dedent`import { mock } from "bun:test";
import { join } from "node:path";
import { PGlite } from "@electric-sql/pglite";
import { drizzle } from "drizzle-orm/pglite";
import { migrate } from "drizzle-orm/pglite/migrator";
${imports.join("\n")}
console.time("PGLite init");
const pglite = new PGlite();
export const db = drizzle(pglite);
mock.module("${driverNames[driver]}", () => ({ default: () => pglite }));
mock.module("drizzle-orm/${driverNamesToDrizzle[driver]}", () => ({ drizzle }));
${mocks.join("\n")}
await migrate(db, {
migrationsFolder: join(import.meta.dir, "..", "drizzle"),
});
console.timeEnd("PGLite init");
`;
}
function getTestsAPIFile({ redis, driver }) {
return dedent`import { treaty } from "@elysiajs/eden";
import { app } from "../src/server.ts";
export const api = treaty(app);`;
}
function getTestsIndex({ redis, driver }) {
return dedent`import { describe, it, expect } from "bun:test";
import { api } from "../api.ts";
describe("API - /", () => {
it("/ - should return hello world", async () => {
const response = await api.index.get();
expect(response.status).toBe(200);
expect(response.data).toBe("Hello World");
});
});
`;
}
function getTestSharedFile() {
return dedent`import { signInitData } from "@gramio/init-data";
export const BOT_TOKEN = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ";
export const INIT_DATA = signInitData(
{
user: {
id: 1,
first_name: "durov",
username: "durov",
},
},
BOT_TOKEN,
);
`;
}
const linterExtensionTag = {
ESLint: "dbaeumer.vscode-eslint",
Biome: "biomejs.biome"
};
function getVSCodeExtensions({
linter,
packageManager,
docker,
orm
}) {
const extensionsFile = {
// just best general purpose extensions and i guess they useful
recommendations: [
"usernamehw.errorlens",
"YoavBls.pretty-ts-errors",
"meganrogge.template-string-converter"
]
};
if (packageManager === "bun")
extensionsFile.recommendations.push("oven.bun-vscode");
if (linter !== "None")
extensionsFile.recommendations.push(linterExtensionTag[linter]);
if (docker)
extensionsFile.recommendations.push("ms-azuretools.vscode-docker");
if (orm === "Drizzle")
extensionsFile.recommendations.push("rphlmr.vscode-drizzle-orm");
if (orm === "Prisma") extensionsFile.recommendations.push("Prisma.prisma");
return JSON.stringify(extensionsFile, null, 2);
}
function getVSCodeSettings({ linter }) {
let settingsFile = {
"editor.formatOnSave": true
};
if (linter !== "None")
settingsFile = {
...settingsFile,
"[javascript]": {
"editor.defaultFormatter": linterExtensionTag[linter]
},
"[typescript]": {
"editor.defaultFormatter": linterExtensionTag[linter]
}
};
return JSON.stringify(settingsFile, null, 2);
}
const preferences = new Preferences();
const args = minimist(process.argv.slice(2));
const packageManager = args.pm || detectPackageManager();
if (packageManager !== "bun") throw new Error("Now supported only bun");
const dir = args._.at(0);
if (!dir)
throw new Error(
"Specify the folder like this - bun create elysiajs dir-name"
);
const projectDir = path.resolve(`${process.cwd()}/`, dir);
process.on("unhandledRejection", async (error) => {
const filesInTargetDirectory = await fs.readdir(projectDir);
if (filesInTargetDirectory.length) {
console.log(error);
const { overwrite } = await enquirer.prompt({
type: "toggle",
name: "overwrite",
initial: "yes",
message: `You exit the process. Do you want to delete the directory ${path.basename(projectDir)}?`
});
if (!overwrite) {
console.log("Cancelled...");
return process.exit(0);
}
}
console.log("Template deleted...");
console.error(error);
await fs.rm(projectDir, { recursive: true });
process.exit(0);
});
createOrFindDir(projectDir).catch((e) => {
console.error(e);
process.exit(1);
}).then(async () => {
preferences.dir = dir;
preferences.projectName = path.basename(projectDir);
preferences.packageManager = packageManager;
preferences.isMonorepo = !!args.monorepo;
preferences.runtime = packageManager === "bun" ? "Bun" : "Node.js";
preferences.noInstall = !Boolean(args.install ?? true);
const filesInTargetDirectory = await fs.readdir(projectDir);
if (filesInTargetDirectory.length) {
const { overwrite } = await enquirer.prompt({
type: "toggle",
name: "overwrite",
initial: "yes",
message: `
${filesInTargetDirectory.join(
"\n"
)}
The directory ${preferences.projectName} is not empty. Do you want to delete the files?`
});
if (!overwrite) {
console.log("Cancelled...");
return process.exit(0);
}
await fs.rm(projectDir, { recursive: true });
await fs.mkdir(projectDir);
}
if (!args.monorepo) {
const { telegramRelated } = await enquirer.prompt({
type: "toggle",
name: "telegramRelated",
initial: "no",
message: "Is your project related to Telegram (Did you wants to validate init data and etc)?"
});
preferences.telegramRelated = telegramRelated;
const { linter } = await enquirer.prompt({
type: "select",
name: "linter",
message: "Select linters/formatters:",
choices: ["None", "ESLint", "Biome"]
});
preferences.linter = linter;
const { orm } = await enquirer.prompt({
type: "select",
name: "orm",
message: "Select ORM/Query Builder:",
choices: ["None", "Prisma", "Drizzle"]
});
preferences.orm = orm;
if (orm === "Prisma") {
const { database } = await enquirer.prompt({
type: "select",
name: "database",
message: "Select DataBase for Prisma:",
choices: [
"PostgreSQL",
"MySQL",
"MongoDB",
"SQLite",
"SQLServer",
"CockroachDB"
]
});
preferences.database = database;
}
if (orm === "Drizzle") {
const { database } = await enquirer.prompt({
type: "select",
name: "database",
message: "Select DataBase for Drizzle:",
choices: ["PostgreSQL", "MySQL", "SQLite"]
});
const driversMap = {
PostgreSQL: [
preferences.runtime === "Bun" ? "Bun.sql" : void 0,
"node-postgres",
"Postgres.JS"
].filter((x) => x !== void 0),
MySQL: ["MySQL 2"],
SQLite: ["Bun SQLite"]
};
const { driver } = await enquirer.prompt({
type: "select",
name: "driver",
message: `Select driver for ${database}:`,
choices: driversMap[database]
});
preferences.database = database;
preferences.driver = driver;
if (database === "PostgreSQL") {
const { mockWithPGLite } = await enquirer.prompt({
type: "toggle",
name: "mockWithPGLite",
initial: "yes",
message: "Do you want to mock database in tests with PGLite (Postgres in WASM)?"
});
preferences.mockWithPGLite = mockWithPGLite;
}
}
} else {
preferences.telegramRelated = true;
}
const { plugins } = await enquirer.prompt({
type: "multiselect",
name: "plugins",
message: "Select Elysia plugins: (Space to select, Enter to continue)",
choices: [
"CORS",
"Swagger",
"JWT",
"Autoload",
"Oauth 2.0",
"Logger",
"HTML/JSX",
"Static",
"Bearer",
"Server Timing"
]
});
preferences.plugins = plugins;
if (!args.monorepo) {
const { others } = await enquirer.prompt({
type: "multiselect",
name: "others",
message: "Select others tools: (Space to select, Enter to continue)",
choices: ["S3", "Posthog", "Jobify", "Husky"]
});
preferences.others = others;
if (others.includes("S3")) {
const { s3Client } = await enquirer.prompt({
type: "select",
name: "s3Client",
message: "Select S3 client:",
choices: ["Bun.S3Client", "@aws-sdk/client-s3"]
});
preferences.s3Client = s3Client;
}
if (!others.includes("Husky")) {
const { git } = await enquirer.prompt({
type: "toggle",
name: "git",
initial: "yes",
message: "Create an empty Git repository?"
});
preferences.git = git;
} else preferences.git = true;
const { locks } = await enquirer.prompt({
type: "toggle",
name: "locks",
initial: "yes",
message: "Do you want to use Locks to prevent race conditions?"
});
preferences.locks = locks;
if (others.includes("Jobify")) {
preferences.redis = true;
} else {
const { redis } = await enquirer.prompt({
type: "toggle",
name: "redis",
initial: "yes",
message: "Do you want to use Redis?"
});
preferences.redis = redis;
}
const { docker } = await enquirer.prompt({
type: "toggle",
name: "docker",
initial: "yes",
message: "Create Dockerfile + docker.compose.yml?"
});
preferences.docker = docker;
const { vscode } = await enquirer.prompt({
type: "toggle",
name: "vscode",
initial: "yes",
message: "Create .vscode folder with VSCode extensions recommendations and settings?"
});
preferences.vscode = vscode;
}
await task("Generating a template...", async ({ setTitle }) => {
if (plugins.includes("Static")) await fs.mkdir(projectDir + "/public");
if (preferences.linter === "ESLint")
await fs.writeFile(
`${projectDir}/eslint.config.mjs`,
generateEslintConfig(preferences)
);
await fs.writeFile(
projectDir + "/package.json",
getPackageJson(preferences)
);
await fs.writeFile(
projectDir + "/tsconfig.json",
getTSConfig(preferences)
);
await fs.writeFile(projectDir + "/.env", getEnvFile(preferences));
await fs.writeFile(
projectDir + "/.env.production",
getEnvFile(preferences, true)
);
await fs.writeFile(projectDir + "/README.md", getReadme(preferences));
await fs.writeFile(
projectDir + "/.gitignore",
["dist", "node_modules", ".env", ".env.production"].join("\n")
);
await fs.mkdir(projectDir + "/src");
await fs.writeFile(
projectDir + "/src/server.ts",
getElysiaIndex(preferences)
);
await fs.writeFile(projectDir + "/src/index.ts", getIndex(preferences));
await fs.writeFile(
`${projectDir}/src/config.ts`,
getConfigFile(preferences)
);
await fs.mkdir(projectDir + "/