├── src ├── templates │ ├── services │ │ ├── jobify.ts │ │ ├── redis.ts │ │ ├── posthog.ts │ │ ├── locks.ts │ │ ├── s3.ts │ │ └── auth.ts │ ├── bot.ts │ ├── install.ts │ ├── eslint.ts │ ├── tsconfig.json.ts │ ├── vscode.ts │ ├── tests.ts │ ├── elysia.ts │ ├── index.ts │ ├── db.ts │ ├── env.ts │ ├── readme.md.ts │ ├── package.json.ts │ └── docker.ts ├── deps.ts ├── utils.ts └── index.ts ├── tsconfig.json ├── biome.json ├── renovate.json ├── package.json ├── README.md ├── .gitignore └── bun.lock /src/templates/services/jobify.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | 3 | export function getJobifyFile() { 4 | return dedent /* ts */` 5 | import { initJobify } from "jobify" 6 | import { redis } from "./redis.ts" 7 | 8 | export const defineJob = initJobify(redis); 9 | `; 10 | } 11 | -------------------------------------------------------------------------------- /src/templates/bot.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | 3 | export function getBotFile() { 4 | return dedent /* ts */` 5 | import { Bot } from "gramio"; 6 | import { config } from "./config.ts"; 7 | 8 | export const bot = new Bot(config.BOT_TOKEN) 9 | .onStart(({ info }) => console.log(\`✨ Bot \${info.username} was started!\`))`; 10 | } 11 | -------------------------------------------------------------------------------- /src/templates/services/redis.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | 3 | export function getRedisFile() { 4 | return dedent /* ts */` 5 | import { Redis } from "ioredis"; 6 | import { config } from "../config.ts" 7 | 8 | export const redis = new Redis({ 9 | host: config.REDIS_HOST, 10 | // for bullmq 11 | maxRetriesPerRequest: null, 12 | }) 13 | `; 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["ESNext"], 4 | "module": "CommonJS", 5 | "target": "esnext", 6 | "declaration": false, 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "skipLibCheck": true, 10 | "allowSyntheticDefaultImports": true, 11 | "rootDir": "./src", 12 | "outDir": "./dist" 13 | }, 14 | "include": ["src"] 15 | } -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.5.2/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "style": { 10 | "useTemplate": "off" 11 | }, 12 | "performance": { 13 | "noDelete": "off" 14 | }, 15 | "recommended": true 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/templates/services/posthog.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | 3 | export function getPosthogIndex() { 4 | return dedent /* ts */` 5 | import { PostHog } from "posthog-node"; 6 | import { config } from "../config.ts"; 7 | 8 | export const posthog = new PostHog(config.POSTHOG_API_KEY, { 9 | host: config.POSTHOG_HOST, 10 | disabled: config.NODE_ENV !== "production", 11 | }); 12 | 13 | posthog.on("error", (err) => { 14 | console.error("PostHog had an error!", err) 15 | }) 16 | `; 17 | } 18 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "automergeSchedule": [ 4 | "before 4am" 5 | ], 6 | "automerge": true, 7 | "extends": ["config:recommended", ":dependencyDashboard"], 8 | "lockFileMaintenance": { 9 | "enabled": true, 10 | "automerge": true 11 | }, 12 | "bumpVersion": "patch", 13 | "labels": ["bump"], 14 | "customManagers": [{ 15 | "fileMatch": ["deps.ts$"], 16 | "datasourceTemplate": "npm", 17 | "matchStrings": ["\t\"?(?.*?)\"?: \"\\^?(?.*?)\""], 18 | "autoReplaceStringTemplate": "\t\"{{{depName}}}\": \"^{{{newValue}}}\"" 19 | }] 20 | } -------------------------------------------------------------------------------- /src/templates/install.ts: -------------------------------------------------------------------------------- 1 | import type { Preferences } from "../utils"; 2 | 3 | export function getInstallCommands({ 4 | linter, 5 | orm, 6 | database, 7 | git, 8 | others, 9 | }: Preferences) { 10 | const commands: string[] = []; 11 | 12 | if (git) commands.push("git init"); 13 | commands.push("bun install"); 14 | if (others.includes("Husky") && linter !== "None") 15 | commands.push(`echo "bun lint:fix" > .husky/pre-commit`); 16 | if (orm === "Prisma") 17 | commands.push( 18 | `bunx prisma init --datasource-provider ${database.toLowerCase()}`, 19 | ); 20 | if (linter === "Biome") commands.push("bunx @biomejs/biome init"); 21 | if (linter !== "None") commands.push("bun lint:fix"); 22 | 23 | return commands; 24 | } 25 | -------------------------------------------------------------------------------- /src/templates/eslint.ts: -------------------------------------------------------------------------------- 1 | import type { PreferencesType } from "../utils.js"; 2 | 3 | export function generateEslintConfig({ orm }: PreferencesType) { 4 | return [ 5 | `import antfu from "@antfu/eslint-config"`, 6 | orm === "Drizzle" && `import drizzle from "eslint-plugin-drizzle";`, 7 | ` 8 | export default antfu( 9 | { 10 | stylistic: { 11 | indent: 2, 12 | quotes: "double", 13 | }, 14 | }, 15 | { 16 | files: ["**/*.js", "**/*.ts"], 17 | rules: { 18 | "node/prefer-global/process": "off", 19 | "no-console": "off", 20 | "antfu/no-top-level-await": "off", 21 | },`, 22 | orm === "Drizzle" && 23 | `plugins: { 24 | drizzle, 25 | },`, 26 | ` 27 | }, 28 | ); 29 | `, 30 | ] 31 | .filter(Boolean) 32 | .join("\n"); 33 | } 34 | -------------------------------------------------------------------------------- /src/templates/tsconfig.json.ts: -------------------------------------------------------------------------------- 1 | import type { Preferences } from "../utils"; 2 | 3 | export function getTSConfig({ plugins }: Preferences) { 4 | return JSON.stringify( 5 | { 6 | compilerOptions: { 7 | lib: ["ESNext"], 8 | module: "NodeNext", 9 | target: "ESNext", 10 | moduleResolution: "NodeNext", 11 | esModuleInterop: true, 12 | strict: true, 13 | skipLibCheck: true, 14 | allowSyntheticDefaultImports: true, 15 | noEmit: true, 16 | allowImportingTsExtensions: true, 17 | noUncheckedIndexedAccess: true, 18 | ...(plugins.includes("HTML/JSX") 19 | ? { 20 | jsx: "react", 21 | jsxFactory: "Html.createElement", 22 | jsxFragmentFactory: "Html.Fragment", 23 | plugins: [{ name: "@kitajs/ts-html-plugin" }], 24 | } 25 | : {}), 26 | }, 27 | include: ["src"], 28 | }, 29 | null, 30 | 2, 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/templates/services/locks.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | import type { PreferencesType } from "../../utils"; 3 | 4 | export function getLocksFile({ redis }: PreferencesType) { 5 | const imports: string[] = []; 6 | const stores: string[] = []; 7 | 8 | stores.push("memory: { driver: memoryStore() }"); 9 | imports.push(`import { memoryStore } from '@verrou/core/drivers/memory'`); 10 | 11 | if (redis) { 12 | stores.push("redis: { driver: redisStore({ connection: redis }) },"); 13 | imports.push(`import { redisStore } from '@verrou/core/drivers/redis'`); 14 | imports.push(`import { redis } from './redis.ts'`); 15 | } 16 | 17 | return dedent /* ts */` 18 | import { Verrou } from "@verrou/core" 19 | import { config } from "../config.ts" 20 | ${imports.join("\n")} 21 | 22 | export const verrou = new Verrou({ 23 | default: config.LOCK_STORE, 24 | stores: { 25 | ${stores.join(",\n")} 26 | } 27 | }) 28 | `; 29 | } 30 | -------------------------------------------------------------------------------- /src/templates/services/s3.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | import type { Preferences } from "../../utils.ts"; 3 | 4 | export function getS3ServiceFile({ s3Client }: Preferences) { 5 | if (s3Client === "Bun.S3Client") { 6 | return dedent /* ts */` 7 | import { S3Client } from "bun"; 8 | import { config } from "../config.ts"; 9 | 10 | export const s3 = new S3Client({ 11 | endpoint: config.S3_ENDPOINT, 12 | accessKeyId: config.S3_ACCESS_KEY_ID, 13 | secretAccessKey: config.S3_SECRET_ACCESS_KEY, 14 | }); 15 | `; 16 | } 17 | 18 | if (s3Client === "@aws-sdk/client-s3") { 19 | return dedent /* ts */` 20 | import { S3Client } from "@aws-sdk/client-s3"; 21 | import { config } from "../config.ts"; 22 | 23 | export const s3 = new S3Client({ 24 | endpoint: config.S3_ENDPOINT, 25 | region: "minio", 26 | credentials: { 27 | accessKeyId: config.S3_ACCESS_KEY_ID, 28 | secretAccessKey: config.S3_SECRET_ACCESS_KEY, 29 | }, 30 | }); 31 | `; 32 | } 33 | 34 | return ""; 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-elysiajs", 3 | "type": "commonjs", 4 | "version": "1.7.0", 5 | "description": "Scaffolding your Elysia project with the environment with easy!", 6 | "author": "kravetsone", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/kravetsone/create-elysiajs.git" 10 | }, 11 | "readme": "https://github.com/kravetsone/create-elysiajs", 12 | "keywords": [ 13 | "elysia", 14 | "elysiajs", 15 | "scaffolding", 16 | "create", 17 | "template", 18 | "lint", 19 | "eslint", 20 | "biome", 21 | "orm", 22 | "prisma", 23 | "drizzle", 24 | "husky" 25 | ], 26 | "files": ["dist"], 27 | "bin": { 28 | "create-elysiajs": "dist/index.js" 29 | }, 30 | "scripts": { 31 | "watch": "bunx pkgroll -w", 32 | "lint": "bunx @biomejs/biome check src", 33 | "lint:fix": "bun lint --apply", 34 | "prepublishOnly": "bunx pkgroll" 35 | }, 36 | "devDependencies": { 37 | "@biomejs/biome": "^2.0.0", 38 | "@types/bun": "^1.2.5", 39 | "@types/minimist": "^1.2.5", 40 | "husky": "^9.1.7", 41 | "pkgroll": "^2.11.2", 42 | "ts-dedent": "^2.2.0", 43 | "typescript": "^5.8.2" 44 | }, 45 | "dependencies": { 46 | "enquirer": "^2.4.1", 47 | "minimist": "^1.2.8", 48 | "tasuku": "^2.0.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/deps.ts: -------------------------------------------------------------------------------- 1 | export const dependencies = { 2 | "elysia": "^1.4.16", 3 | 4 | "typescript": "^5.9.3", 5 | 6 | "@types/bun": "^1.3.2", 7 | 8 | "@biomejs/biome": "^2.3.5", 9 | 10 | "eslint": "^9.39.1", 11 | 12 | "eslint-plugin-drizzle": "^0.2.3", 13 | 14 | "prisma": "^6.19.0", 15 | 16 | "@prisma/client": "^6.19.0", 17 | 18 | "drizzle-orm": "^0.44.7", 19 | 20 | "drizzle-kit": "^0.31.7", 21 | 22 | "pg": "^8.16.3", 23 | 24 | "@types/pg": "^8.15.6", 25 | 26 | "postgres": "^3.4.7", 27 | 28 | "mysql2": "^3.15.3", 29 | 30 | husky: "^9.1.7", 31 | 32 | "@elysiajs/bearer": "^1.4.1", 33 | 34 | "@elysiajs/cors": "^1.4.0", 35 | 36 | "@elysiajs/html": "^1.4.0", 37 | 38 | "@kitajs/ts-html-plugin": "^4.1.3", 39 | 40 | "@elysiajs/jwt": "^1.4.0", 41 | 42 | "@elysiajs/server-timing": "^1.4.0", 43 | 44 | "@elysiajs/static": "^1.4.6", 45 | 46 | "@elysiajs/swagger": "^1.3.1", 47 | 48 | "elysia-autoload": "^1.7.0", 49 | 50 | "@bogeychan/elysia-logger": "^0.1.10", 51 | 52 | "@antfu/eslint-config": "^6.2.0", 53 | 54 | "@gramio/init-data": "^0.0.5", 55 | 56 | "elysia-oauth2": "^2.1.0", 57 | 58 | "arctic": "^3.7.0", 59 | 60 | "env-var": "^7.5.0", 61 | 62 | "posthog-node": "^5.11.2", 63 | 64 | jobify: "^0.1.6", 65 | 66 | "ioredis": "^5.8.2", 67 | 68 | "@verrou/core": "^0.5.1", 69 | 70 | "@aws-sdk/client-s3": "^3.930.0", 71 | 72 | "@elysiajs/eden": "^1.4.5", 73 | 74 | "ioredis-mock": "^8.13.1", 75 | 76 | "@electric-sql/pglite": "^0.3.14", 77 | 78 | "gramio": "^0.4.11", 79 | }; 80 | -------------------------------------------------------------------------------- /src/templates/services/auth.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | 3 | export function getAuthPlugin() { 4 | return dedent /* ts */` 5 | import { validateAndParseInitData, signInitData, getBotTokenSecretKey } from "@gramio/init-data"; 6 | import { Elysia, t } from "elysia"; 7 | import { config } from "../config.ts"; 8 | 9 | const secretKey = getBotTokenSecretKey(config.BOT_TOKEN); 10 | 11 | export const authElysia = new Elysia({ 12 | name: "auth", 13 | }) 14 | .guard({ 15 | headers: t.Object({ 16 | "x-init-data": t.String({ 17 | examples: [ 18 | signInitData( 19 | { 20 | user: { 21 | id: 1, 22 | first_name: "durov", 23 | username: "durov", 24 | }, 25 | }, 26 | secretKey 27 | ), 28 | ], 29 | }), 30 | }), 31 | response: { 32 | 401: t.Literal("UNAUTHORIZED"), 33 | }, 34 | }) 35 | .resolve(({ headers, error }) => { 36 | const result = validateAndParseInitData( 37 | headers["x-init-data"], 38 | secretKey 39 | ); 40 | if (!result || !result.user) 41 | return error("Unauthorized", "UNAUTHORIZED"); 42 | 43 | return { 44 | tgId: result.user.id, 45 | user: result.user, 46 | }; 47 | }) 48 | .as("plugin");`; 49 | } 50 | -------------------------------------------------------------------------------- /src/templates/vscode.ts: -------------------------------------------------------------------------------- 1 | import type { PreferencesType } from "../utils.js"; 2 | 3 | const linterExtensionTag: Record< 4 | Exclude, 5 | string 6 | > = { 7 | ESLint: "dbaeumer.vscode-eslint", 8 | Biome: "biomejs.biome", 9 | }; 10 | 11 | export function getVSCodeExtensions({ 12 | linter, 13 | packageManager, 14 | docker, 15 | orm, 16 | }: PreferencesType) { 17 | const extensionsFile: { recommendations: string[] } = { 18 | // just best general purpose extensions and i guess they useful 19 | recommendations: [ 20 | "usernamehw.errorlens", 21 | "YoavBls.pretty-ts-errors", 22 | "meganrogge.template-string-converter", 23 | ], 24 | }; 25 | 26 | if (packageManager === "bun") 27 | extensionsFile.recommendations.push("oven.bun-vscode"); 28 | 29 | if (linter !== "None") 30 | extensionsFile.recommendations.push(linterExtensionTag[linter]); 31 | 32 | if (docker) 33 | extensionsFile.recommendations.push("ms-azuretools.vscode-docker"); 34 | 35 | if (orm === "Drizzle") 36 | extensionsFile.recommendations.push("rphlmr.vscode-drizzle-orm"); 37 | if (orm === "Prisma") extensionsFile.recommendations.push("Prisma.prisma"); 38 | 39 | return JSON.stringify(extensionsFile, null, 2); 40 | } 41 | 42 | export function getVSCodeSettings({ linter }: PreferencesType) { 43 | let settingsFile: Record = { 44 | "editor.formatOnSave": true, 45 | }; 46 | 47 | if (linter !== "None") 48 | settingsFile = { 49 | ...settingsFile, 50 | "[javascript]": { 51 | "editor.defaultFormatter": linterExtensionTag[linter], 52 | }, 53 | "[typescript]": { 54 | "editor.defaultFormatter": linterExtensionTag[linter], 55 | }, 56 | }; 57 | 58 | return JSON.stringify(settingsFile, null, 2); 59 | } 60 | -------------------------------------------------------------------------------- /src/templates/tests.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | import type { Preferences } from "../utils"; 3 | import { driverNames } from "./db"; 4 | import { driverNamesToDrizzle } from "./db"; 5 | 6 | export function getPreloadFile({ redis, driver }: Preferences) { 7 | const imports: string[] = []; 8 | const mocks: string[] = []; 9 | 10 | if (redis) { 11 | imports.push('import redis from "ioredis-mock"'); 12 | mocks.push( 13 | "mock.module('ioredis', () => ({ Redis: redis, default: redis }))", 14 | ); 15 | } 16 | 17 | return dedent /* ts */`import { mock } from "bun:test"; 18 | import { join } from "node:path"; 19 | import { PGlite } from "@electric-sql/pglite"; 20 | import { drizzle } from "drizzle-orm/pglite"; 21 | import { migrate } from "drizzle-orm/pglite/migrator"; 22 | ${imports.join("\n")} 23 | 24 | console.time("PGLite init"); 25 | 26 | const pglite = new PGlite(); 27 | export const db = drizzle(pglite); 28 | 29 | mock.module("${driverNames[driver]}", () => ({ default: () => pglite })); 30 | 31 | mock.module("drizzle-orm/${driverNamesToDrizzle[driver]}", () => ({ drizzle })); 32 | ${mocks.join("\n")} 33 | 34 | await migrate(db, { 35 | migrationsFolder: join(import.meta.dir, "..", "drizzle"), 36 | }); 37 | 38 | console.timeEnd("PGLite init"); 39 | `; 40 | } 41 | 42 | export function getTestsAPIFile({ redis, driver }: Preferences) { 43 | return dedent /* ts */`import { treaty } from "@elysiajs/eden"; 44 | import { app } from "../src/server.ts"; 45 | 46 | export const api = treaty(app);`; 47 | } 48 | 49 | export function getTestsIndex({ redis, driver }: Preferences) { 50 | return dedent /* ts */`import { describe, it, expect } from "bun:test"; 51 | import { api } from "../api.ts"; 52 | 53 | describe("API - /", () => { 54 | it("/ - should return hello world", async () => { 55 | const response = await api.index.get(); 56 | 57 | expect(response.status).toBe(200); 58 | expect(response.data).toBe("Hello World"); 59 | }); 60 | }); 61 | 62 | `; 63 | } 64 | 65 | export function getTestSharedFile() { 66 | return dedent /* ts */`import { signInitData } from "@gramio/init-data"; 67 | 68 | export const BOT_TOKEN = "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 69 | 70 | export const INIT_DATA = signInitData( 71 | { 72 | user: { 73 | id: 1, 74 | first_name: "durov", 75 | username: "durov", 76 | }, 77 | }, 78 | BOT_TOKEN, 79 | ); 80 | `; 81 | } 82 | -------------------------------------------------------------------------------- /src/templates/elysia.ts: -------------------------------------------------------------------------------- 1 | import type { Preferences } from "../utils"; 2 | 3 | export function getElysiaIndex({ 4 | orm, 5 | driver, 6 | plugins, 7 | telegramRelated, 8 | isMonorepo, 9 | }: Preferences) { 10 | const elysiaPlugins: string[] = []; 11 | const elysiaImports: string[] = [ 12 | `import { Elysia } from "elysia"`, 13 | `import { config } from "./config.ts"`, 14 | ]; 15 | 16 | if (plugins.includes("Logger")) { 17 | elysiaImports.push(`import { logger } from "@bogeychan/elysia-logger"`); 18 | elysiaPlugins.push(".use(logger())"); 19 | } 20 | 21 | if (plugins.includes("Swagger")) { 22 | elysiaImports.push(`import { swagger } from "@elysiajs/swagger"`); 23 | elysiaPlugins.push(".use(swagger())"); 24 | } 25 | if (plugins.includes("Oauth 2.0")) { 26 | elysiaImports.push(`import { oauth2 } from "elysia-oauth2"`); 27 | elysiaPlugins.push(".use(oauth2({}))"); 28 | } 29 | if (plugins.includes("Bearer")) { 30 | elysiaImports.push(`import { bearer } from "@elysiajs/bearer"`); 31 | elysiaPlugins.push(".use(bearer())"); 32 | } 33 | if (plugins.includes("CORS")) { 34 | elysiaImports.push(`import { cors } from "@elysiajs/cors"`); 35 | elysiaPlugins.push(".use(cors())"); 36 | } 37 | if (plugins.includes("HTML/JSX")) { 38 | elysiaImports.push(`import { html } from "@elysiajs/html"`); 39 | elysiaPlugins.push(".use(html())"); 40 | } 41 | if (plugins.includes("JWT")) { 42 | elysiaImports.push(`import { jwt } from "@elysiajs/jwt"`); 43 | elysiaPlugins.push(".use(jwt({ secret: config.JWT_SECRET }))"); 44 | } 45 | if (plugins.includes("Server Timing")) { 46 | elysiaImports.push( 47 | `import { serverTiming } from "@elysiajs/server-timing"`, 48 | ); 49 | elysiaPlugins.push(".use(serverTiming())"); 50 | } 51 | if (plugins.includes("Static")) { 52 | elysiaImports.push(`import { staticPlugin } from "@elysiajs/static"`); 53 | elysiaPlugins.push(".use(staticPlugin())"); 54 | } 55 | if (plugins.includes("Autoload")) { 56 | elysiaImports.push(`import { autoload } from "elysia-autoload"`); 57 | elysiaPlugins.push(".use(autoload())"); 58 | } 59 | 60 | elysiaPlugins.push(`.get("/", "Hello World")`); 61 | 62 | if (telegramRelated && !isMonorepo) { 63 | elysiaImports.push(`import { bot } from "./bot.ts"`); 64 | elysiaImports.push(`import { webhookHandler } from "./services/auth.ts"`); 65 | elysiaPlugins.push( 66 | `.post(\`/\${config.BOT_TOKEN}\`, webhookHandler(bot, "elysia"), { 67 | detail: { 68 | hide: true, 69 | }, 70 | })`, 71 | ); 72 | } 73 | 74 | return [ 75 | ...elysiaImports, 76 | "", 77 | "export const app = new Elysia()", 78 | ...elysiaPlugins, 79 | plugins.includes("Autoload") ? "\nexport type ElysiaApp = typeof app" : "", 80 | ].join("\n"); 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-elysiajs 2 | 3 |
4 | 5 | [![npm](https://img.shields.io/npm/v/create-elysiajs?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/create-elysiajs) 6 | [![npm downloads](https://img.shields.io/npm/dw/create-elysiajs?logo=npm&style=flat&labelColor=000&color=3b82f6)](https://www.npmjs.org/package/create-elysiajs) 7 | 8 |
9 | 10 | ## Scaffolding your [Elysia](https://elysiajs.com/) project with the environment with easy! 11 | 12 | ### With [bun](https://bun.sh/) 13 | 14 | ```bash 15 | bun create elysiajs 16 | ``` 17 | 18 | > Support for other package managers will appear later (Maybe, huh) 19 | 20 | ## Supported environment 21 | 22 | - Linters 23 | - - [Biome](https://biomejs.dev/) 24 | - - [ESLint](https://eslint.org/) with [@antfu/eslint-config](https://eslint-config.antfu.me/rules) 25 | - ORM/Query builders 26 | - - [Prisma](https://www.prisma.io/) 27 | - - [Drizzle](https://orm.drizzle.team/) 28 | - Plugins 29 | - - [CORS](https://elysiajs.com/plugins/cors.html) 30 | - - [Swagger](https://elysiajs.com/plugins/swagger.html) 31 | - - [JWT](https://elysiajs.com/plugins/jwt.html) 32 | - - [Autoload](https://github.com/kravetsone/elysia-autoload) 33 | - - [Oauth 2.0](https://github.com/kravetsone/elysia-oauth2) 34 | - - [HTML/JSX](https://elysiajs.com/plugins/html.html) 35 | - - [Logger](https://github.com/bogeychan/elysia-logger) 36 | - - [Static](https://elysiajs.com/plugins/static.html) 37 | - - [Bearer](https://elysiajs.com/plugins/bearer.html) 38 | - - [Server Timing](https://elysiajs.com/plugins/server-timing.html) 39 | - Test with bun:test and mocks for 40 | - - [PGLite](https://pglite.dev/) for Postgres 41 | - - [IoRedisMock](https://www.npmjs.com/package/ioredis-mock) for Redis 42 | - Others 43 | - - [Dockerfile](https://www.docker.com/) + [docker-compose.yml](https://docs.docker.com/compose/) 44 | - - [Jobify](https://github.com/kravetsone/jobify) ([Bullmq](https://docs.bullmq.io/) wrapper) 45 | - - [Posthog](https://posthog.com/docs/libraries/node) 46 | - - [Verrou](https://github.com/kravetsone/verrou) (Locks) 47 | - - [Env-var](https://github.com/evanshortiss/env-var) (Environment variables) 48 | - - [.vscode](https://code.visualstudio.com/) (VSCode settings) 49 | - - [Husky](https://typicode.github.io/husky/) (Git hooks) 50 | - And more soon... 51 | 52 | > With renovate, we keep dependencies up to date 53 | 54 | > The environment can work `together` 55 | > 56 | > When you select [ESLint](https://eslint.org/) and [Drizzle](https://orm.drizzle.team/), you get [eslint-plugin-drizzle](https://orm.drizzle.team/docs/eslint-plugin) 57 | > 58 | > When you select [Husky](https://typicode.github.io/husky/) and one of the [linters](#supported-environment) - the `pre-commit` hook will contain the command `lint:fix` 59 | -------------------------------------------------------------------------------- /src/templates/index.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | import type { PreferencesType } from "../utils"; 3 | 4 | export * from "./package.json"; 5 | export * from "./elysia"; 6 | export * from "./install"; 7 | export * from "./db"; 8 | export * from "./tsconfig.json"; 9 | export * from "./env"; 10 | export * from "./readme.md"; 11 | export * from "./eslint"; 12 | 13 | const dbExportedMap = { 14 | Prisma: "prisma", 15 | Drizzle: "client", 16 | }; 17 | 18 | export function getIndex({ 19 | others, 20 | orm, 21 | driver, 22 | telegramRelated, 23 | isMonorepo, 24 | }: PreferencesType) { 25 | const isShouldConnectToDB = 26 | orm !== "None" && 27 | driver !== "Postgres.JS" && 28 | driver !== "MySQL 2" && 29 | driver !== "Bun SQLite" && 30 | driver !== "Bun.sql"; 31 | 32 | const gracefulShutdownTasks: string[] = []; 33 | const imports: string[] = [ 34 | // `import { bot } from "./bot.ts"`, 35 | `import { config } from "./config.ts"`, 36 | ]; 37 | const startUpTasks: string[] = []; 38 | 39 | imports.push(`import { app } from "./server.ts"`); 40 | gracefulShutdownTasks.push("await app.stop()"); 41 | 42 | // TODO: GramIO integration 43 | // gracefulShutdownTasks.push("await bot.stop()"); 44 | 45 | if (others.includes("Posthog")) { 46 | imports.push(`import { posthog } from "./services/posthog.ts"`); 47 | gracefulShutdownTasks.push("await posthog.shutdown()"); 48 | } 49 | 50 | if (isShouldConnectToDB) { 51 | imports.push(`import { ${dbExportedMap[orm]} } from "./db/index.ts"`); 52 | startUpTasks.push(dedent /* ts */` 53 | ${orm === "Prisma" ? "await prisma.$connect()" : "await client.connect()"} 54 | console.log("🗄️ Database was connected!")`); 55 | } 56 | 57 | startUpTasks.push(/*ts*/ ` 58 | app.listen(config.PORT, () => console.log(\`🦊 Server started at \${app.server?.url.origin}\`)) 59 | `); 60 | 61 | if (telegramRelated && !isMonorepo) { 62 | imports.push(`import { bot } from "./bot.ts"`); 63 | startUpTasks.push(dedent /* tss */` 64 | if (config.NODE_ENV === "production") 65 | await bot.start({ 66 | webhook: { 67 | url: \`\${config.API_URL}/\${config.BOT_TOKEN}\`, 68 | }, 69 | }); 70 | else await bot.start();`); 71 | } 72 | 73 | return dedent /* sts */` 74 | ${imports.join("\n")} 75 | const signals = ["SIGINT", "SIGTERM"]; 76 | 77 | for (const signal of signals) { 78 | process.on(signal, async () => { 79 | console.log(\`Received \${signal}. Initiating graceful shutdown...\`); 80 | ${gracefulShutdownTasks.join("\n")} 81 | process.exit(0); 82 | }) 83 | } 84 | 85 | process.on("uncaughtException", (error) => { 86 | console.error(error); 87 | }) 88 | 89 | process.on("unhandledRejection", (error) => { 90 | console.error(error); 91 | }) 92 | 93 | ${startUpTasks.join("\n")}`; 94 | } 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | test -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import child_process from "node:child_process"; 2 | import { randomBytes } from "node:crypto"; 3 | import fs from "node:fs/promises"; 4 | import { promisify } from "node:util"; 5 | 6 | export type PackageManager = "bun" | "npm" | "yarn" | "pnpm"; 7 | 8 | const nodeMajorVersion = process?.versions?.node?.split(".")[0]; 9 | 10 | if (nodeMajorVersion && Number(nodeMajorVersion) < 22) 11 | console.warn( 12 | `Node.js version ${process?.versions?.node} is not recommended for this template. Please upgrade to Node.js 22 or higher.`, 13 | ); 14 | 15 | export function detectPackageManager() { 16 | const userAgent = process.env.npm_config_user_agent; 17 | 18 | if (!userAgent) 19 | throw new Error( 20 | `Package manager was not detected. Please specify template with "--pm bun"`, 21 | ); 22 | 23 | return userAgent.split(" ")[0].split("/")[0] as PackageManager; 24 | } 25 | 26 | export async function createOrFindDir(path: string) { 27 | await fs.stat(path).catch(async () => fs.mkdir(path)); 28 | } 29 | 30 | export class Preferences { 31 | projectName = ""; 32 | dir = ""; 33 | packageManager: PackageManager = "bun"; 34 | runtime: "Bun" | "Node.js" = "Bun"; 35 | linter: "ESLint" | "Biome" | "None" = "None"; 36 | orm: "Prisma" | "Drizzle" | "None" = "None"; 37 | database: 38 | | "PostgreSQL" 39 | | "MySQL" 40 | | "MongoDB" 41 | | "SQLite" 42 | | "SQLServer" 43 | | "CockroachDB" = "PostgreSQL"; 44 | driver: 45 | | "node-postgres" 46 | | "Bun.sql" 47 | | "Postgres.JS" 48 | | "MySQL 2" 49 | | "Bun SQLite" 50 | | "None" = "None"; 51 | git = true; 52 | others: ("S3" | "Husky" | "Posthog" | "Jobify")[] = []; 53 | plugins: ( 54 | | "JWT" 55 | | "CORS" 56 | | "Swagger" 57 | | "Autoload" 58 | | "Oauth 2.0" 59 | | "Logger" 60 | | "HTML/JSX" 61 | | "Static" 62 | | "Bearer" 63 | | "Server Timing" 64 | )[] = []; 65 | // integration with create-gramio 66 | isMonorepo = false; 67 | 68 | docker = false; 69 | vscode = false; 70 | redis = false; 71 | locks = false; 72 | s3Client: "Bun.S3Client" | "@aws-sdk/client-s3" | "None" = "None"; 73 | meta: { 74 | databasePassword: string; 75 | } = { 76 | databasePassword: randomBytes(12).toString("hex"), 77 | }; 78 | 79 | noInstall = false; 80 | 81 | mockWithPGLite = false; 82 | 83 | telegramRelated = false; 84 | } 85 | 86 | export type PreferencesType = InstanceType; 87 | 88 | export const exec = promisify(child_process.exec); 89 | 90 | export const pmExecuteMap: Record = { 91 | npm: "npx", 92 | bun: "bun x", 93 | yarn: "yarn dlx", 94 | pnpm: "pnpm dlx", 95 | }; 96 | 97 | export const pmRunMap: Record = { 98 | npm: "npm run", 99 | bun: "bun", 100 | yarn: "yarn", 101 | pnpm: "pnpm", 102 | }; 103 | 104 | export const pmFilterMonorepoMap: Record = { 105 | npm: false, 106 | yarn: false, 107 | bun: "bun --filter 'apps/*'", 108 | pnpm: "pnpm --filter 'apps/*'", 109 | }; 110 | 111 | export const pmLockFilesMap: Record = { 112 | npm: "package.lock.json", 113 | bun: "bun.lock", 114 | yarn: "yarn.lock", 115 | pnpm: "pnpm-lock.yaml", 116 | }; 117 | 118 | export const pmInstallFrozenLockfile: Record = { 119 | npm: "npm ci", 120 | bun: "bun install --frozen-lockfile", 121 | yarn: "yarn install --frozen-lockfile", 122 | pnpm: "pnpm install --frozen-lockfile", 123 | }; 124 | 125 | export const pmInstallFrozenLockfileProduction: Record = 126 | { 127 | npm: "npm ci --production", 128 | bun: "bun install --frozen-lockfile --production", 129 | yarn: "yarn install --frozen-lockfile --production", 130 | pnpm: "pnpm install --frozen-lockfile --prod", 131 | }; 132 | -------------------------------------------------------------------------------- /src/templates/db.ts: -------------------------------------------------------------------------------- 1 | import type { Preferences } from "../utils.js"; 2 | 3 | export const driverNamesToDrizzle: Record = { 4 | "node-postgres": "node-postgres", 5 | "Bun.sql": "bun-sql", 6 | "Postgres.JS": "postgres-js", 7 | "MySQL 2": "mysql2", 8 | "Bun SQLite": "bun-sqlite", 9 | None: "", 10 | }; 11 | 12 | export const driverNames: Record = { 13 | "node-postgres": "pg", 14 | "Bun.sql": "??", 15 | "Postgres.JS": "postgres", 16 | "MySQL 2": "mysql2", 17 | "Bun SQLite": "bun:sqlite", 18 | None: "", 19 | }; 20 | export function getDBIndex({ orm, driver, packageManager }: Preferences) { 21 | if (orm === "Prisma") 22 | return [ 23 | `import { PrismaClient } from "@prisma/client"`, 24 | "", 25 | "export const prisma = new PrismaClient()", 26 | "", 27 | `export * from "@prisma/client"`, 28 | ].join("\n"); 29 | 30 | if (driver === "node-postgres") 31 | return [ 32 | `import { drizzle } from "drizzle-orm/node-postgres"`, 33 | `import { Client } from "pg"`, 34 | `import { config } from "../config.ts"`, 35 | "", 36 | "export const client = new Client({", 37 | " connectionString: config.DATABASE_URL,", 38 | "})", 39 | "", 40 | "export const db = drizzle({", 41 | " client,", 42 | ' casing: "snake_case",', 43 | "})", 44 | ].join("\n"); 45 | 46 | if (driver === "Postgres.JS") 47 | return [ 48 | `import { drizzle } from "drizzle-orm/postgres-js"`, 49 | `import postgres from "postgres"`, 50 | `import { config } from "../config.ts"`, 51 | "", 52 | "const client = postgres(config.DATABASE_URL)", 53 | "export const db = drizzle({", 54 | " client,", 55 | ' casing: "snake_case",', 56 | "})", 57 | ].join("\n"); 58 | 59 | if (driver === "Bun.sql") 60 | return [ 61 | `import { drizzle } from "drizzle-orm/bun-sql"`, 62 | `import { config } from "../config.ts"`, 63 | `import { SQL } from "bun"`, 64 | "", 65 | "export const sql = new SQL(config.DATABASE_URL)", 66 | "", 67 | "export const db = drizzle({", 68 | " client: sql,", 69 | ' casing: "snake_case",', 70 | "})", 71 | ].join("\n"); 72 | 73 | if (driver === "MySQL 2") 74 | return [ 75 | `import { drizzle } from "drizzle-orm/mysql2"`, 76 | `import mysql from "mysql2/promise"`, 77 | `import { config } from "../config.ts"`, 78 | "", 79 | "export const connection = await mysql.createConnection(config.DATABASE_URL)", 80 | `console.log("🗄️ Database was connected!")`, 81 | "", 82 | "export const db = drizzle({", 83 | " client: connection,", 84 | ' casing: "snake_case",', 85 | "})", 86 | ].join("\n"); 87 | 88 | if (driver === "Bun SQLite" && packageManager === "bun") 89 | return [ 90 | `import { drizzle } from "drizzle-orm/bun-sqlite"`, 91 | `import { Database } from "bun:sqlite";`, 92 | "", 93 | `export const sqlite = new Database("sqlite.db")`, 94 | "export const db = drizzle({", 95 | " client: sqlite,", 96 | ' casing: "snake_case",', 97 | "})", 98 | ].join("\n"); 99 | 100 | return [ 101 | `import { drizzle } from "drizzle-orm/better-sqlite3`, 102 | `import { Database } from "better-sqlite3";`, 103 | "", 104 | `export const sqlite = new Database("sqlite.db")`, 105 | "export const db = drizzle({", 106 | " client: sqlite,", 107 | ' casing: "snake_case",', 108 | "})", 109 | ].join("\n"); 110 | } 111 | 112 | export function getDrizzleConfig({ database }: Preferences) { 113 | return [ 114 | `import type { Config } from "drizzle-kit"`, 115 | `import env from "env-var"`, 116 | "", 117 | 'const DATABASE_URL = env.get("DATABASE_URL").required().asString()', 118 | "", 119 | "export default {", 120 | ` schema: "./src/db/schema.ts",`, 121 | ` out: "./drizzle",`, 122 | ` dialect: "${database.toLowerCase()}",`, 123 | ` casing: "snake_case",`, 124 | " dbCredentials: {", 125 | " url: DATABASE_URL", 126 | " }", 127 | "} satisfies Config", 128 | ].join("\n"); 129 | } 130 | -------------------------------------------------------------------------------- /src/templates/env.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from "node:crypto"; 2 | import dedent from "ts-dedent"; 3 | import type { Preferences, PreferencesType } from "../utils.js"; 4 | 5 | const connectionURLExamples: Record< 6 | InstanceType["database"], 7 | string 8 | > = { 9 | PostgreSQL: "postgresql://root:mypassword@localhost:5432/mydb", 10 | MySQL: "mysql://root:mypassword@localhost:3306/mydb", 11 | SQLServer: 12 | "sqlserver://localhost:1433;database=mydb;user=root;password=mypassword;", 13 | CockroachDB: 14 | "postgresql://root:mypassword@localhost:26257/mydb?schema=public", 15 | MongoDB: 16 | "mongodb+srv://root:mypassword@cluster0.ab1cd.mongodb.net/mydb?retryWrites=true&w=majority", 17 | SQLite: "file:./sqlite.db", 18 | }; 19 | 20 | const composeServiceNames: Record< 21 | InstanceType["database"], 22 | string 23 | > = { 24 | PostgreSQL: "postgres", 25 | MySQL: "localhost", 26 | SQLServer: "localhost", 27 | CockroachDB: "localhost", 28 | MongoDB: "localhost", 29 | SQLite: "file:./sqlite.db", 30 | }; 31 | 32 | export function getEnvFile( 33 | { 34 | database, 35 | orm, 36 | plugins, 37 | projectName, 38 | redis, 39 | meta, 40 | telegramRelated, 41 | }: PreferencesType, 42 | isComposed = false, 43 | ) { 44 | const envs = []; 45 | 46 | if (orm !== "None") { 47 | let url = connectionURLExamples[database] 48 | .replace("mydb", projectName) 49 | .replace("root", projectName) 50 | .replace("mypassword", meta.databasePassword); 51 | 52 | // rename localhost to docker compose service name in network 53 | if (isComposed) 54 | url = url.replace("localhost", composeServiceNames[database]); 55 | 56 | envs.push(`DATABASE_URL="${url}"`); 57 | } 58 | 59 | if (telegramRelated) { 60 | envs.push(`BOT_TOKEN=""`); 61 | } 62 | 63 | if (isComposed && redis) envs.push("REDIS_HOST=redis"); 64 | 65 | if (plugins.includes("JWT")) 66 | envs.push(`JWT_SECRET="${randomBytes(12).toString("hex")}"`); 67 | 68 | envs.push("PORT=3000"); 69 | return envs.join("\n"); 70 | } 71 | 72 | export function getConfigFile({ 73 | orm, 74 | redis, 75 | others, 76 | plugins, 77 | locks, 78 | telegramRelated, 79 | }: PreferencesType) { 80 | const envs: string[] = []; 81 | 82 | envs.push(`PORT: env.get("PORT").default(3000).asPortNumber()`); 83 | // envs.push(`PUBLIC_DOMAIN: env.get("PUBLIC_DOMAIN").asString()`); 84 | envs.push( 85 | `API_URL: env.get("API_URL").default(\`https://\${env.get("PUBLIC_DOMAIN").asString()}\`).asString()`, 86 | ); 87 | 88 | if (telegramRelated) { 89 | envs.push(`BOT_TOKEN: env.get("BOT_TOKEN").required().asString()`); 90 | } 91 | 92 | if (orm !== "None") 93 | envs.push(`DATABASE_URL: env.get("DATABASE_URL").required().asString()`); 94 | 95 | if (redis) { 96 | envs.push( 97 | `REDIS_HOST: env.get("REDIS_HOST").default("localhost").asString()`, 98 | ); 99 | } 100 | 101 | if (others.includes("Posthog")) { 102 | envs.push( 103 | `POSTHOG_API_KEY: env.get("POSTHOG_API_KEY").default("it's a secret").asString()`, 104 | ); 105 | envs.push( 106 | `POSTHOG_HOST: env.get("POSTHOG_HOST").default("localhost").asString()`, 107 | ); 108 | } 109 | 110 | if (others.includes("S3")) { 111 | envs.push( 112 | `S3_ENDPOINT: env.get("S3_ENDPOINT").default("localhost").asString()`, 113 | ); 114 | envs.push( 115 | `S3_ACCESS_KEY_ID: env.get("S3_ACCESS_KEY_ID").default("minio").asString()`, 116 | ); 117 | envs.push( 118 | `S3_SECRET_ACCESS_KEY: env.get("S3_SECRET_ACCESS_KEY").default("minio").asString()`, 119 | ); 120 | } 121 | 122 | if (locks) { 123 | const stores = ["memory"]; 124 | if (redis) stores.push("redis"); 125 | 126 | envs.push( 127 | `LOCK_STORE: env.get("LOCK_STORE").default("memory").asEnum(${JSON.stringify(stores)})`, 128 | ); 129 | } 130 | 131 | if (plugins.includes("JWT")) 132 | envs.push(`JWT_SECRET: env.get("JWT_SECRET").required().asString()`); 133 | 134 | return dedent /* ts */` 135 | import env from "env-var"; 136 | 137 | export const config = { 138 | NODE_ENV: env 139 | .get("NODE_ENV") 140 | .default("development") 141 | .asEnum(["production", "test", "development"]), 142 | 143 | 144 | ${envs.join(",\n")} 145 | }`; 146 | } 147 | -------------------------------------------------------------------------------- /src/templates/readme.md.ts: -------------------------------------------------------------------------------- 1 | import type { Preferences, PreferencesType } from "../utils"; 2 | 3 | const links: Record< 4 | | Exclude< 5 | | "ElysiaJS" 6 | | PreferencesType["linter"] 7 | | PreferencesType["orm"] 8 | | PreferencesType["plugins"][0] 9 | | PreferencesType["others"][0] 10 | | PreferencesType["database"], 11 | "None" 12 | > 13 | | "Jobify" 14 | | "Docker" 15 | | "PGLite" 16 | | "Bun" 17 | | "Redis" 18 | | "IoRedisMock", 19 | string 20 | > = { 21 | Bun: "[Bun](https://bun.sh/)", 22 | ElysiaJS: "[ElysiaJS](https://elysiajs.com/)", 23 | ESLint: "[ESLint](https://eslint.org/)", 24 | Biome: "[Biome](https://biomejs.dev/)", 25 | Prisma: "[Prisma](https://www.prisma.io/)", 26 | Drizzle: "[Drizzle](https://orm.drizzle.team/)", 27 | CORS: "[CORS](https://elysiajs.com/plugins/cors.html)", 28 | Swagger: "[Swagger](https://elysiajs.com/plugins/swagger.html)", 29 | JWT: "[JWT](https://elysiajs.com/plugins/jwt.html)", 30 | Autoload: "[Autoload](https://github.com/kravetsone/elysia-autoload)", 31 | "Oauth 2.0": "[Oauth 2.0](https://github.com/kravetsone/elysia-oauth2)", 32 | Logger: "[Logger](https://github.com/bogeychan/elysia-logger)", 33 | "HTML/JSX": "[HTML/JSX](https://elysiajs.com/plugins/html.html)", 34 | Static: "[Static](https://elysiajs.com/plugins/static.html)", 35 | Bearer: "[Bearer](https://elysiajs.com/plugins/bearer.html)", 36 | "Server Timing": 37 | "[Server Timing](https://elysiajs.com/plugins/server-timing.html)", 38 | Husky: "[Husky](https://typicode.github.io/husky/)", 39 | PostgreSQL: "[PostgreSQL](https://www.postgresql.org/)", 40 | MySQL: "[MySQL](https://www.mysql.com/)", 41 | MongoDB: "[MongoDB](https://www.mongodb.com/)", 42 | SQLite: "[SQLite](https://sqlite.org/)", 43 | SQLServer: "[SQLServer](https://www.microsoft.com/sql-server)", 44 | CockroachDB: "[CockroachDB](https://www.cockroachlabs.com/)", 45 | Jobify: "[Jobify](https://github.com/kravetsone/jobify)", 46 | Docker: "[Docker](https://www.docker.com/)", 47 | Posthog: "[Posthog](https://posthog.com/docs/libraries/node)", 48 | PGLite: "[PGLite](https://pglite.dev/)", 49 | S3: "[Minio](https://github.com/minio/minio)", 50 | Redis: 51 | "[Redis](https://redis.io/) + [ioredis](https://github.com/redis/ioredis)", 52 | IoRedisMock: "[ioredis-mock](https://www.npmjs.com/package/ioredis-mock)", 53 | }; 54 | 55 | const TESTS_REPO_LINK = "[tests](tree/main/tests)"; 56 | 57 | export function getReadme({ 58 | dir, 59 | linter, 60 | orm, 61 | database, 62 | plugins, 63 | others, 64 | docker, 65 | mockWithPGLite, 66 | redis, 67 | }: Preferences) { 68 | const stack = []; 69 | 70 | stack.push(`- Web framework - ${links.ElysiaJS}`); 71 | if (linter !== "None") stack.push(`- Linter - ${links[linter]}`); 72 | if (orm !== "None") 73 | stack.push( 74 | `- ORM - ${links[orm]} (${links[database]})${ 75 | mockWithPGLite 76 | ? ` (mocked with ${links.PGLite} in ${TESTS_REPO_LINK})` 77 | : "" 78 | }`, 79 | ); 80 | if (plugins.length) 81 | stack.push(`- Elysia plugins - ${plugins.map((x) => links[x]).join(", ")}`); 82 | if (others.length) 83 | stack.push( 84 | `- Others tools - ${[ 85 | docker ? links.Docker : undefined, 86 | redis 87 | ? mockWithPGLite 88 | ? `${links.Redis} + ${links.IoRedisMock} in tests` 89 | : links.Redis 90 | : undefined, 91 | ...others.map((x) => links[x]), 92 | ] 93 | .filter(Boolean) 94 | .join(", ")}`, 95 | ); 96 | 97 | const instruction = []; 98 | 99 | instruction.push("## Development\n"); 100 | 101 | if (docker) { 102 | instruction.push( 103 | "Start development services (DB, Redis etc):\n", 104 | "```bash", 105 | "docker compose -f docker-compose.dev.yml up", 106 | "```\n", 107 | ); 108 | } 109 | 110 | instruction.push("Start the project:\n", "```bash", "bun dev", "```\n"); 111 | 112 | if (orm === "Drizzle") { 113 | instruction.push( 114 | "## Migrations\n", 115 | "Push schema to Database:\n", 116 | "```bash", 117 | "bunx drizzle-kit push", 118 | "```", 119 | "Generate new migration:\n", 120 | "```bash", 121 | "bunx drizzle-kit generate", 122 | "```", 123 | "Apply migrations:\n", 124 | "```bash", 125 | "bunx drizzle-kit migrate", 126 | "```\n", 127 | ); 128 | } 129 | 130 | if (orm === "Prisma") { 131 | instruction.push( 132 | "## Migrations\n", 133 | "Generate new migration:\n", 134 | "```bash", 135 | "bunx prisma migrate dev", 136 | "```", 137 | "Apply migrations:\n", 138 | "```bash", 139 | "bunx prisma migrate deploy", 140 | "```\n", 141 | ); 142 | } 143 | 144 | if (mockWithPGLite) { 145 | instruction.push( 146 | "## Tests\n", 147 | `Tests are written with ${links.Bun}:test.\n`, 148 | "Mocks:\n", 149 | `- Postgres usage is mocked with ${links.PGLite}`, 150 | `- Redis usage is mocked with ${links.IoRedisMock}`, 151 | "\n", 152 | "```bash", 153 | "bun test", 154 | "```\n", 155 | ); 156 | } 157 | 158 | instruction.push("## Production\n"); 159 | 160 | if (docker) { 161 | instruction.push( 162 | "Run project in `production` mode:\n", 163 | "```bash", 164 | "docker compose up -d", 165 | "```", 166 | ); 167 | } else 168 | instruction.push( 169 | "Run project in `production` mode:\n", 170 | "```bash", 171 | "bun start", 172 | "```", 173 | ); 174 | 175 | return [ 176 | `# ${dir}`, 177 | "", 178 | "This template autogenerated by [create-elysiajs](https://github.com/kravetsone/create-elysiajs)", 179 | "", 180 | "### Stack", 181 | ...stack, 182 | "", 183 | // "### Instructions", 184 | ...instruction, 185 | ].join("\n"); 186 | } 187 | -------------------------------------------------------------------------------- /src/templates/package.json.ts: -------------------------------------------------------------------------------- 1 | import { dependencies } from "../deps"; 2 | import { type Preferences, pmExecuteMap, pmRunMap } from "../utils"; 3 | 4 | export function getPackageJson({ 5 | dir, 6 | projectName, 7 | linter, 8 | packageManager, 9 | orm, 10 | driver, 11 | others, 12 | plugins, 13 | isMonorepo, 14 | locks, 15 | redis, 16 | mockWithPGLite, 17 | telegramRelated, 18 | s3Client, 19 | }: Preferences) { 20 | const sample = { 21 | name: projectName, 22 | type: "module", 23 | scripts: { 24 | dev: 25 | packageManager === "bun" 26 | ? "bun --watch src/index.ts" 27 | : `${pmExecuteMap[packageManager]} tsx watch --env-file .env src/index.ts`, 28 | start: 29 | packageManager === "bun" 30 | ? "NODE_ENV=production bun run ./src/index.ts" 31 | : `NODE_ENV=production ${pmExecuteMap[packageManager]} tsx --env-file=.env --env-file=.env.production src/index.ts`, 32 | } as Record, 33 | dependencies: { 34 | elysia: dependencies.elysia, 35 | "env-var": dependencies["env-var"], 36 | } as Record, 37 | devDependencies: { 38 | typescript: dependencies.typescript, 39 | } as Record, 40 | }; 41 | 42 | // if (packageManager === "bun") 43 | sample.devDependencies["@types/bun"] = dependencies["@types/bun"]; 44 | 45 | if (linter === "Biome") { 46 | sample.scripts.lint = `${pmExecuteMap[packageManager]} @biomejs/biome check src`; 47 | sample.scripts["lint:fix"] = `${pmRunMap[packageManager]} lint --write`; 48 | sample.devDependencies["@biomejs/biome"] = dependencies["@biomejs/biome"]; 49 | } 50 | if (linter === "ESLint") { 51 | // \"src/**/*.ts\" 52 | sample.scripts.lint = `${pmExecuteMap[packageManager]} eslint`; 53 | // \"src/**/*.ts\" 54 | sample.scripts["lint:fix"] = `${pmExecuteMap[packageManager]} eslint --fix`; 55 | sample.devDependencies.eslint = dependencies.eslint; 56 | sample.devDependencies["@antfu/eslint-config"] = 57 | dependencies["@antfu/eslint-config"]; 58 | if (orm === "Drizzle") 59 | sample.devDependencies["eslint-plugin-drizzle"] = 60 | dependencies["eslint-plugin-drizzle"]; 61 | } 62 | 63 | if (orm === "Prisma") { 64 | sample.devDependencies.prisma = dependencies.prisma; 65 | sample.dependencies["@prisma/client"] = dependencies["@prisma/client"]; 66 | } 67 | if (orm === "Drizzle") { 68 | sample.dependencies["drizzle-orm"] = dependencies["drizzle-orm"]; 69 | sample.devDependencies["drizzle-kit"] = dependencies["drizzle-kit"]; 70 | if (driver === "node-postgres") { 71 | sample.dependencies.pg = dependencies.pg; 72 | sample.devDependencies["@types/pg"] = dependencies["@types/pg"]; 73 | } 74 | if (driver === "Postgres.JS") { 75 | sample.dependencies.postgres = dependencies.postgres; 76 | } 77 | if (driver === "MySQL 2") { 78 | sample.dependencies.mysql2 = dependencies.mysql2; 79 | } 80 | sample.scripts.generate = `${pmExecuteMap[packageManager]} drizzle-kit generate`; 81 | sample.scripts.push = `${pmExecuteMap[packageManager]} drizzle-kit push`; 82 | sample.scripts.migrate = `${pmExecuteMap[packageManager]} drizzle-kit migrate`; 83 | sample.scripts.studio = `${pmExecuteMap[packageManager]} drizzle-kit studio`; 84 | } 85 | 86 | if (others.includes("Husky")) { 87 | sample.devDependencies.husky = dependencies.husky; 88 | sample.scripts.prepare = "husky"; 89 | } 90 | 91 | if (plugins.includes("Bearer")) 92 | sample.dependencies["@elysiajs/bearer"] = dependencies["@elysiajs/bearer"]; 93 | if (plugins.includes("CORS")) 94 | sample.dependencies["@elysiajs/cors"] = dependencies["@elysiajs/cors"]; 95 | if (plugins.includes("HTML/JSX")) { 96 | sample.dependencies["@elysiajs/html"] = dependencies["@elysiajs/html"]; 97 | sample.dependencies["@kitajs/ts-html-plugin"] = 98 | dependencies["@kitajs/ts-html-plugin"]; 99 | } 100 | if (plugins.includes("JWT")) 101 | sample.dependencies["@elysiajs/jwt"] = dependencies["@elysiajs/jwt"]; 102 | if (plugins.includes("Server Timing")) 103 | sample.dependencies["@elysiajs/server-timing"] = 104 | dependencies["@elysiajs/server-timing"]; 105 | if (plugins.includes("Static")) 106 | sample.dependencies["@elysiajs/static"] = dependencies["@elysiajs/static"]; 107 | if (plugins.includes("Swagger")) 108 | sample.dependencies["@elysiajs/swagger"] = 109 | dependencies["@elysiajs/swagger"]; 110 | if (plugins.includes("Autoload")) 111 | sample.dependencies["elysia-autoload"] = dependencies["elysia-autoload"]; 112 | if (plugins.includes("Logger")) 113 | sample.dependencies["@bogeychan/elysia-logger"] = 114 | dependencies["@bogeychan/elysia-logger"]; 115 | 116 | if (plugins.includes("Oauth 2.0")) { 117 | sample.dependencies.arctic = dependencies.arctic; 118 | sample.dependencies["elysia-oauth2"] = dependencies["elysia-oauth2"]; 119 | } 120 | 121 | if (redis) { 122 | sample.dependencies.ioredis = dependencies.ioredis; 123 | if (mockWithPGLite) 124 | sample.devDependencies["ioredis-mock"] = dependencies["ioredis-mock"]; 125 | } 126 | 127 | if (others.includes("Jobify")) { 128 | sample.dependencies.jobify = dependencies.jobify; 129 | } 130 | 131 | if (others.includes("Posthog")) { 132 | sample.dependencies["posthog-node"] = dependencies["posthog-node"]; 133 | } 134 | 135 | if (locks) { 136 | sample.dependencies["@verrou/core"] = dependencies["@verrou/core"]; 137 | } 138 | 139 | if (isMonorepo) 140 | sample.dependencies["@gramio/init-data"] = 141 | dependencies["@gramio/init-data"]; 142 | 143 | if (others.includes("S3") && s3Client === "@aws-sdk/client-s3") { 144 | sample.dependencies["@aws-sdk/client-s3"] = 145 | dependencies["@aws-sdk/client-s3"]; 146 | } 147 | 148 | if (mockWithPGLite) { 149 | sample.devDependencies["@electric-sql/pglite"] = 150 | dependencies["@electric-sql/pglite"]; 151 | sample.devDependencies["@elysiajs/eden"] = dependencies["@elysiajs/eden"]; 152 | } 153 | 154 | if (telegramRelated && !isMonorepo) { 155 | sample.dependencies.gramio = dependencies.gramio; 156 | sample.dependencies["@gramio/init-data"] = 157 | dependencies["@gramio/init-data"]; 158 | } 159 | 160 | return JSON.stringify(sample, null, 2); 161 | } 162 | -------------------------------------------------------------------------------- /src/templates/docker.ts: -------------------------------------------------------------------------------- 1 | import dedent from "ts-dedent"; 2 | import { 3 | type Preferences, 4 | type PreferencesType, 5 | pmExecuteMap, 6 | pmInstallFrozenLockfile, 7 | pmInstallFrozenLockfileProduction, 8 | pmLockFilesMap, 9 | pmRunMap, 10 | } from "../utils.js"; 11 | 12 | const ormDockerCopy: Record, string> = { 13 | Prisma: "COPY --from=prerelease /usr/src/app/prisma ./prisma", 14 | Drizzle: dedent` 15 | COPY --from=prerelease /usr/src/app/drizzle ./drizzle 16 | COPY --from=prerelease /usr/src/app/drizzle.config.ts .`, 17 | }; 18 | 19 | export function getDockerfile({ packageManager, orm }: Preferences) { 20 | if (packageManager === "bun") 21 | return dedent /* Dockerfile */` 22 | # use the official Bun image 23 | # see all versions at https://hub.docker.com/r/oven/bun/tags 24 | FROM oven/bun:${process.versions.bun ?? "1.3.2"} AS base 25 | WORKDIR /usr/src/app 26 | 27 | # install dependencies into temp directory 28 | # this will cache them and speed up future builds 29 | FROM base AS install 30 | RUN mkdir -p /temp/dev 31 | COPY package.json bun.lock /temp/dev/ 32 | RUN cd /temp/dev && bun install --frozen-lockfile 33 | 34 | # install with --production (exclude devDependencies) 35 | RUN mkdir -p /temp/prod 36 | COPY package.json bun.lock /temp/prod/ 37 | RUN cd /temp/prod && bun install --frozen-lockfile --production 38 | 39 | # copy node_modules from temp directory 40 | # then copy all (non-ignored) project files into the image 41 | FROM base AS prerelease 42 | COPY --from=install /temp/dev/node_modules node_modules 43 | COPY . . 44 | 45 | ENV NODE_ENV=production 46 | RUN ${pmExecuteMap[packageManager]} tsc --noEmit 47 | 48 | # copy production dependencies and source code into final image 49 | FROM base AS release 50 | COPY --from=install /temp/prod/node_modules node_modules 51 | COPY --from=prerelease /usr/src/app/${pmLockFilesMap[packageManager]} . 52 | RUN mkdir -p /usr/src/app/src 53 | COPY --from=prerelease /usr/src/app/src ./src 54 | COPY --from=prerelease /usr/src/app/package.json . 55 | COPY --from=prerelease /usr/src/app/tsconfig.json . 56 | ${orm !== "None" ? ormDockerCopy[orm] : ""} 57 | 58 | ENTRYPOINT [ "bun", "start" ]`; 59 | 60 | return dedent /* Dockerfile */` 61 | # Use the official Node.js 22 image. 62 | # See https://hub.docker.com/_/node for more information. 63 | FROM node:${process?.versions?.node ?? "22.12"} AS base 64 | 65 | # Create app directory 66 | WORKDIR /usr/src/app 67 | 68 | ${packageManager !== "npm" ? "npm install ${packageManager} -g" : ""} 69 | # Install dependencies into temp directory 70 | # This will cache them and speed up future builds 71 | FROM base AS install 72 | RUN mkdir -p /temp/dev 73 | COPY package.json ${pmLockFilesMap[packageManager]} /temp/dev/ 74 | RUN cd /temp/dev && ${pmInstallFrozenLockfile[packageManager]} 75 | 76 | # Install with --production (exclude devDependencies) 77 | RUN mkdir -p /temp/prod 78 | COPY package.json ${pmLockFilesMap[packageManager]} /temp/prod/ 79 | RUN cd /temp/prod && ${pmInstallFrozenLockfileProduction[packageManager]} 80 | 81 | # Copy node_modules from temp directory 82 | # Then copy all (non-ignored) project files into the image 83 | FROM base AS prerelease 84 | COPY --from=install /temp/dev/node_modules node_modules 85 | COPY . . 86 | 87 | ENV NODE_ENV=production 88 | 89 | RUN ${pmExecuteMap[packageManager]} tsc --noEmit 90 | 91 | # Copy production dependencies and source code into final image 92 | FROM base AS release 93 | COPY --from=install /temp/prod/node_modules node_modules 94 | RUN mkdir -p /usr/src/app/src 95 | COPY --from=prerelease /usr/src/app/src ./src 96 | COPY --from=prerelease /usr/src/app/${pmLockFilesMap[packageManager]} . 97 | COPY --from=prerelease /usr/src/app/package.json . 98 | COPY --from=prerelease /usr/src/app/tsconfig.json . 99 | ${orm !== "None" ? ormDockerCopy[orm] : ""} 100 | 101 | # TODO:// should be downloaded not at ENTRYPOINT 102 | ENTRYPOINT [ "${pmRunMap[packageManager]}", "start" ]`; 103 | } 104 | 105 | // TODO: generate redis+postgres 106 | export function getDockerCompose({ 107 | database, 108 | redis, 109 | projectName, 110 | meta, 111 | others, 112 | }: PreferencesType) { 113 | const volumes: string[] = []; 114 | 115 | if (database === "PostgreSQL") volumes.push("postgres_data:"); 116 | if (redis) volumes.push("redis_data:"); 117 | if (others.includes("S3")) volumes.push("minio_data:"); 118 | 119 | const services = [ 120 | /* yaml */ `bot: 121 | container_name: ${projectName}-bot 122 | restart: unless-stopped 123 | build: 124 | context: . 125 | dockerfile: Dockerfile 126 | environment: 127 | - NODE_ENV=production`, 128 | database === "PostgreSQL" 129 | ? /* yaml */ `postgres: 130 | container_name: ${projectName}-postgres 131 | image: postgres:latest 132 | restart: unless-stopped 133 | environment: 134 | - POSTGRES_USER=${projectName} 135 | - POSTGRES_PASSWORD=${meta.databasePassword} 136 | - POSTGRES_DB=${projectName} 137 | volumes: 138 | - postgres_data:/var/lib/postgresql/data` 139 | : "", 140 | redis 141 | ? /* yaml */ `redis: 142 | container_name: ${projectName}-redis 143 | image: redis:latest 144 | command: [ "redis-server", "--maxmemory-policy", "noeviction" ] 145 | restart: unless-stopped 146 | volumes: 147 | - redis_data:/data` 148 | : "", 149 | others.includes("S3") 150 | ? /* yaml */ `minio: 151 | container_name: ${projectName}-minio 152 | image: minio/minio:latest 153 | command: [ "minio", "server", "/data", "--console-address", ":9001" ] 154 | restart: unless-stopped 155 | environment: 156 | - MINIO_ACCESS_KEY=${projectName} 157 | - MINIO_SECRET_KEY=${meta.databasePassword} 158 | ports: 159 | - 9000:9000 160 | - 9001:9001 161 | volumes: 162 | - minio_data:/data 163 | healthcheck: 164 | test: ["CMD", "mc", "ready", "local"] 165 | interval: 5s 166 | timeout: 5s 167 | retries: 5` 168 | : "", 169 | ]; 170 | 171 | return dedent /* yaml */` 172 | services: 173 | ${services.filter(Boolean).join("\n")} 174 | volumes: 175 | ${volumes.join("\n")} 176 | 177 | networks: 178 | default: {} 179 | `; 180 | } 181 | 182 | export function getDevelopmentDockerCompose({ 183 | database, 184 | redis, 185 | projectName, 186 | meta, 187 | others, 188 | }: PreferencesType) { 189 | const volumes: string[] = []; 190 | 191 | if (database === "PostgreSQL") volumes.push("postgres_data:"); 192 | if (redis) volumes.push("redis_data:"); 193 | if (others.includes("S3")) volumes.push("minio_data:"); 194 | 195 | const services = [ 196 | database === "PostgreSQL" 197 | ? /* yaml */ `postgres: 198 | container_name: ${projectName}-postgres 199 | image: postgres:latest 200 | restart: unless-stopped 201 | environment: 202 | - POSTGRES_USER=${projectName} 203 | - POSTGRES_PASSWORD=${meta.databasePassword} 204 | - POSTGRES_DB=${projectName} 205 | ports: 206 | - 5432:5432 207 | volumes: 208 | - postgres_data:/var/lib/postgresql/data` 209 | : "", 210 | redis 211 | ? /* yaml */ `redis: 212 | container_name: ${projectName}-redis 213 | image: redis:latest 214 | command: [ "redis-server", "--maxmemory-policy", "noeviction" ] 215 | restart: unless-stopped 216 | ports: 217 | - 6379:6379 218 | volumes: 219 | - redis_data:/data` 220 | : "", 221 | others.includes("S3") 222 | ? /* yaml */ `minio: 223 | container_name: ${projectName}-minio 224 | image: minio/minio:latest 225 | command: [ "minio", "server", "/data", "--console-address", ":9001" ] 226 | restart: unless-stopped 227 | environment: 228 | - MINIO_ACCESS_KEY=${projectName} 229 | - MINIO_SECRET_KEY=${meta.databasePassword} 230 | volumes: 231 | - minio_data:/data 232 | healthcheck: 233 | test: ["CMD", "mc", "ready", "local"] 234 | interval: 5s 235 | timeout: 5s 236 | retries: 5` 237 | : "", 238 | ]; 239 | 240 | return dedent /* yaml */` 241 | services: 242 | ${services.filter(Boolean).join("\n")} 243 | volumes: 244 | ${volumes.join("\n")} 245 | 246 | networks: 247 | default: {} 248 | `; 249 | } 250 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import fs from "node:fs/promises"; 3 | import path from "node:path"; 4 | import { prompt } from "enquirer"; 5 | import minimist from "minimist"; 6 | import task from "tasuku"; 7 | import dedent from "ts-dedent"; 8 | import { 9 | generateEslintConfig, 10 | getConfigFile, 11 | getDBIndex, 12 | getDrizzleConfig, 13 | getElysiaIndex, 14 | getEnvFile, 15 | getIndex, 16 | getInstallCommands, 17 | getPackageJson, 18 | getReadme, 19 | getTSConfig, 20 | } from "./templates"; 21 | import { getBotFile } from "./templates/bot"; 22 | import { 23 | getDevelopmentDockerCompose, 24 | getDockerCompose, 25 | getDockerfile, 26 | } from "./templates/docker"; 27 | import { getAuthPlugin } from "./templates/services/auth"; 28 | import { getJobifyFile } from "./templates/services/jobify"; 29 | import { getLocksFile } from "./templates/services/locks"; 30 | import { getPosthogIndex } from "./templates/services/posthog"; 31 | import { getRedisFile } from "./templates/services/redis"; 32 | import { getS3ServiceFile } from "./templates/services/s3"; 33 | import { 34 | getPreloadFile, 35 | getTestSharedFile, 36 | getTestsAPIFile, 37 | getTestsIndex, 38 | } from "./templates/tests"; 39 | import { getVSCodeExtensions, getVSCodeSettings } from "./templates/vscode"; 40 | import { 41 | Preferences, 42 | type PreferencesType, 43 | createOrFindDir, 44 | detectPackageManager, 45 | exec, 46 | } from "./utils"; 47 | 48 | const preferences = new Preferences(); 49 | 50 | const args = minimist(process.argv.slice(2)); 51 | 52 | const packageManager = args.pm || detectPackageManager(); 53 | if (packageManager !== "bun") throw new Error("Now supported only bun"); 54 | 55 | const dir = args._.at(0); 56 | if (!dir) 57 | throw new Error( 58 | "Specify the folder like this - bun create elysiajs dir-name", 59 | ); 60 | const projectDir = path.resolve(`${process.cwd()}/`, dir); 61 | 62 | process.on("unhandledRejection", async (error) => { 63 | const filesInTargetDirectory = await fs.readdir(projectDir); 64 | if (filesInTargetDirectory.length) { 65 | console.log(error); 66 | const { overwrite } = await prompt<{ overwrite: boolean }>({ 67 | type: "toggle", 68 | name: "overwrite", 69 | initial: "yes", 70 | message: `You exit the process. Do you want to delete the directory ${path.basename(projectDir)}?`, 71 | }); 72 | if (!overwrite) { 73 | console.log("Cancelled..."); 74 | return process.exit(0); 75 | } 76 | } 77 | console.log("Template deleted..."); 78 | console.error(error); 79 | await fs.rm(projectDir, { recursive: true }); 80 | process.exit(0); 81 | }); 82 | 83 | createOrFindDir(projectDir) 84 | .catch((e) => { 85 | console.error(e); 86 | process.exit(1); 87 | }) 88 | .then(async () => { 89 | preferences.dir = dir; 90 | preferences.projectName = path.basename(projectDir); 91 | preferences.packageManager = packageManager; 92 | preferences.isMonorepo = !!args.monorepo; 93 | preferences.runtime = packageManager === "bun" ? "Bun" : "Node.js"; 94 | 95 | // biome-ignore lint/complexity/noExtraBooleanCast: 96 | preferences.noInstall = !Boolean(args.install ?? true); 97 | 98 | const filesInTargetDirectory = await fs.readdir(projectDir); 99 | if (filesInTargetDirectory.length) { 100 | const { overwrite } = await prompt<{ overwrite: boolean }>({ 101 | type: "toggle", 102 | name: "overwrite", 103 | initial: "yes", 104 | message: `\n${filesInTargetDirectory.join( 105 | "\n", 106 | )}\n\nThe directory ${preferences.projectName} is not empty. Do you want to delete the files?`, 107 | }); 108 | if (!overwrite) { 109 | console.log("Cancelled..."); 110 | return process.exit(0); 111 | } 112 | 113 | await fs.rm(projectDir, { recursive: true }); 114 | await fs.mkdir(projectDir); 115 | } 116 | 117 | if (!args.monorepo) { 118 | const { telegramRelated } = await prompt<{ 119 | telegramRelated: PreferencesType["telegramRelated"]; 120 | }>({ 121 | type: "toggle", 122 | name: "telegramRelated", 123 | initial: "no", 124 | message: 125 | "Is your project related to Telegram (Did you wants to validate init data and etc)?", 126 | }); 127 | preferences.telegramRelated = telegramRelated; 128 | 129 | const { linter } = await prompt<{ linter: PreferencesType["linter"] }>({ 130 | type: "select", 131 | name: "linter", 132 | message: "Select linters/formatters:", 133 | choices: ["None", "ESLint", "Biome"], 134 | }); 135 | preferences.linter = linter; 136 | 137 | const { orm } = await prompt<{ orm: PreferencesType["orm"] }>({ 138 | type: "select", 139 | name: "orm", 140 | message: "Select ORM/Query Builder:", 141 | choices: ["None", "Prisma", "Drizzle"], 142 | }); 143 | preferences.orm = orm; 144 | if (orm === "Prisma") { 145 | const { database } = await prompt<{ 146 | database: PreferencesType["database"]; 147 | }>({ 148 | type: "select", 149 | name: "database", 150 | message: "Select DataBase for Prisma:", 151 | choices: [ 152 | "PostgreSQL", 153 | "MySQL", 154 | "MongoDB", 155 | "SQLite", 156 | "SQLServer", 157 | "CockroachDB", 158 | ], 159 | }); 160 | preferences.database = database; 161 | } 162 | if (orm === "Drizzle") { 163 | const { database } = await prompt<{ 164 | database: "PostgreSQL" | "MySQL" | "SQLite"; 165 | }>({ 166 | type: "select", 167 | name: "database", 168 | message: "Select DataBase for Drizzle:", 169 | choices: ["PostgreSQL", "MySQL", "SQLite"], 170 | }); 171 | const driversMap: Record = 172 | { 173 | PostgreSQL: ( 174 | [ 175 | preferences.runtime === "Bun" ? "Bun.sql" : undefined, 176 | "node-postgres", 177 | "Postgres.JS", 178 | ] as const 179 | ).filter((x) => x !== undefined), 180 | MySQL: ["MySQL 2"], 181 | SQLite: ["Bun SQLite"], 182 | }; 183 | 184 | const { driver } = await prompt<{ driver: PreferencesType["driver"] }>({ 185 | type: "select", 186 | name: "driver", 187 | message: `Select driver for ${database}:`, 188 | choices: driversMap[database], 189 | }); 190 | preferences.database = database; 191 | preferences.driver = driver; 192 | 193 | if (database === "PostgreSQL") { 194 | const { mockWithPGLite } = await prompt<{ 195 | mockWithPGLite: PreferencesType["mockWithPGLite"]; 196 | }>({ 197 | type: "toggle", 198 | name: "mockWithPGLite", 199 | initial: "yes", 200 | message: 201 | "Do you want to mock database in tests with PGLite (Postgres in WASM)?", 202 | }); 203 | preferences.mockWithPGLite = mockWithPGLite; 204 | } 205 | } 206 | } else { 207 | preferences.telegramRelated = true; 208 | } 209 | const { plugins } = await prompt<{ 210 | plugins: PreferencesType["plugins"]; 211 | }>({ 212 | type: "multiselect", 213 | name: "plugins", 214 | message: "Select Elysia plugins: (Space to select, Enter to continue)", 215 | choices: [ 216 | "CORS", 217 | "Swagger", 218 | "JWT", 219 | "Autoload", 220 | "Oauth 2.0", 221 | // "Logger", 222 | "HTML/JSX", 223 | "Static", 224 | "Bearer", 225 | "Server Timing", 226 | ] as PreferencesType["plugins"], 227 | }); 228 | preferences.plugins = plugins; 229 | if (!args.monorepo) { 230 | const { others } = await prompt<{ others: PreferencesType["others"] }>({ 231 | type: "multiselect", 232 | name: "others", 233 | message: "Select others tools: (Space to select, Enter to continue)", 234 | choices: ["S3", "Posthog", "Jobify", "Husky"], 235 | }); 236 | preferences.others = others; 237 | 238 | if (others.includes("S3")) { 239 | const { s3Client } = await prompt<{ 240 | s3Client: PreferencesType["s3Client"]; 241 | }>({ 242 | type: "select", 243 | name: "s3Client", 244 | message: "Select S3 client:", 245 | choices: ["Bun.S3Client", "@aws-sdk/client-s3"], 246 | }); 247 | preferences.s3Client = s3Client; 248 | } 249 | 250 | if (!others.includes("Husky")) { 251 | const { git } = await prompt<{ git: boolean }>({ 252 | type: "toggle", 253 | name: "git", 254 | initial: "yes", 255 | message: "Create an empty Git repository?", 256 | }); 257 | preferences.git = git; 258 | } else preferences.git = true; 259 | 260 | const { locks } = await prompt<{ locks: boolean }>({ 261 | type: "toggle", 262 | name: "locks", 263 | initial: "yes", 264 | message: "Do you want to use Locks to prevent race conditions?", 265 | }); 266 | 267 | preferences.locks = locks; 268 | 269 | if (others.includes("Jobify")) { 270 | preferences.redis = true; 271 | } else { 272 | const { redis } = await prompt<{ redis: boolean }>({ 273 | type: "toggle", 274 | name: "redis", 275 | initial: "yes", 276 | message: "Do you want to use Redis?", 277 | }); 278 | 279 | preferences.redis = redis; 280 | } 281 | 282 | const { docker } = await prompt<{ docker: boolean }>({ 283 | type: "toggle", 284 | name: "docker", 285 | initial: "yes", 286 | message: "Create Dockerfile + docker.compose.yml?", 287 | }); 288 | 289 | preferences.docker = docker; 290 | 291 | const { vscode } = await prompt<{ vscode: boolean }>({ 292 | type: "toggle", 293 | name: "vscode", 294 | initial: "yes", 295 | message: 296 | "Create .vscode folder with VSCode extensions recommendations and settings?", 297 | }); 298 | 299 | preferences.vscode = vscode; 300 | } 301 | 302 | await task("Generating a template...", async ({ setTitle }) => { 303 | if (plugins.includes("Static")) await fs.mkdir(projectDir + "/public"); 304 | 305 | if (preferences.linter === "ESLint") 306 | await fs.writeFile( 307 | `${projectDir}/eslint.config.mjs`, 308 | generateEslintConfig(preferences), 309 | ); 310 | 311 | await fs.writeFile( 312 | projectDir + "/package.json", 313 | getPackageJson(preferences), 314 | ); 315 | await fs.writeFile( 316 | projectDir + "/tsconfig.json", 317 | getTSConfig(preferences), 318 | ); 319 | await fs.writeFile(projectDir + "/.env", getEnvFile(preferences)); 320 | await fs.writeFile( 321 | projectDir + "/.env.production", 322 | getEnvFile(preferences, true), 323 | ); 324 | await fs.writeFile(projectDir + "/README.md", getReadme(preferences)); 325 | await fs.writeFile( 326 | projectDir + "/.gitignore", 327 | ["dist", "node_modules", ".env", ".env.production"].join("\n"), 328 | ); 329 | await fs.mkdir(projectDir + "/src"); 330 | await fs.writeFile( 331 | projectDir + "/src/server.ts", 332 | getElysiaIndex(preferences), 333 | ); 334 | await fs.writeFile(projectDir + "/src/index.ts", getIndex(preferences)); 335 | await fs.writeFile( 336 | `${projectDir}/src/config.ts`, 337 | getConfigFile(preferences), 338 | ); 339 | 340 | // if (plugins.includes("Autoload")) 341 | await fs.mkdir(projectDir + "/src/routes"); 342 | 343 | if (preferences.orm !== "None") { 344 | await fs.mkdir(projectDir + "/src/db"); 345 | await fs.writeFile( 346 | projectDir + "/src/db/index.ts", 347 | getDBIndex(preferences), 348 | ); 349 | 350 | if (preferences.orm === "Drizzle") { 351 | await fs.writeFile( 352 | projectDir + "/drizzle.config.ts", 353 | getDrizzleConfig(preferences), 354 | ); 355 | await fs.writeFile( 356 | projectDir + "/src/db/schema.ts", 357 | preferences.database === "PostgreSQL" 358 | ? `// import { pgTable } from "drizzle-orm/pg-core"` 359 | : preferences.database === "MySQL" 360 | ? `// import { mysqlTable } from "drizzle-orm/mysql-core"` 361 | : `// import { sqliteTable } from "drizzle-orm/sqlite-core"`, 362 | ); 363 | if (preferences.database === "SQLite") 364 | await fs.writeFile(projectDir + "/sqlite.db", ""); 365 | } 366 | } 367 | 368 | await fs.mkdir(projectDir + "/src/services"); 369 | 370 | if (preferences.others.includes("Posthog")) { 371 | await fs.writeFile( 372 | `${projectDir}/src/services/posthog.ts`, 373 | getPosthogIndex(), 374 | ); 375 | } 376 | 377 | if (preferences.others.includes("Jobify")) { 378 | await fs.writeFile( 379 | `${projectDir}/src/services/jobify.ts`, 380 | getJobifyFile(), 381 | ); 382 | await fs.mkdir(projectDir + "/src/jobs"); 383 | } 384 | 385 | if (preferences.redis) { 386 | await fs.writeFile( 387 | `${projectDir}/src/services/redis.ts`, 388 | getRedisFile(), 389 | ); 390 | } 391 | 392 | if (preferences.locks) { 393 | await fs.writeFile( 394 | `${projectDir}/src/services/locks.ts`, 395 | getLocksFile(preferences), 396 | ); 397 | } 398 | 399 | if (preferences.s3Client !== "None") { 400 | await fs.writeFile( 401 | `${projectDir}/src/services/s3.ts`, 402 | getS3ServiceFile(preferences), 403 | ); 404 | } 405 | 406 | if (preferences.telegramRelated) { 407 | await fs.writeFile( 408 | `${projectDir}/src/services/auth.plugin.ts`, 409 | getAuthPlugin(), 410 | ); 411 | } 412 | if (preferences.docker) { 413 | await fs.writeFile( 414 | `${projectDir}/Dockerfile`, 415 | getDockerfile(preferences), 416 | ); 417 | await fs.writeFile( 418 | `${projectDir}/docker-compose.dev.yml`, 419 | getDevelopmentDockerCompose(preferences), 420 | ); 421 | await fs.writeFile( 422 | `${projectDir}/docker-compose.yml`, 423 | getDockerCompose(preferences), 424 | ); 425 | } 426 | 427 | if (preferences.vscode) { 428 | await fs.mkdir(`${projectDir}/.vscode`); 429 | await fs.writeFile( 430 | `${projectDir}/.vscode/settings.json`, 431 | getVSCodeSettings(preferences), 432 | ); 433 | await fs.writeFile( 434 | `${projectDir}/.vscode/extensions.json`, 435 | getVSCodeExtensions(preferences), 436 | ); 437 | } 438 | 439 | if (preferences.mockWithPGLite) { 440 | await fs.mkdir(projectDir + "/tests"); 441 | await fs.writeFile( 442 | `${projectDir}/tests/preload.ts`, 443 | getPreloadFile(preferences), 444 | ); 445 | await fs.writeFile( 446 | `${projectDir}/tests/api.ts`, 447 | getTestsAPIFile(preferences), 448 | ); 449 | await fs.mkdir(`${projectDir}/tests/e2e`); 450 | await fs.writeFile( 451 | `${projectDir}/tests/e2e/index.test.ts`, 452 | getTestsIndex(preferences), 453 | ); 454 | 455 | await fs.writeFile( 456 | `${projectDir}/bunfig.toml`, 457 | dedent /* toml */`[test] 458 | preload = ["./tests/preload.ts"] 459 | `, 460 | ); 461 | 462 | if (preferences.telegramRelated) 463 | await fs.writeFile( 464 | `${projectDir}/tests/shared.ts`, 465 | getTestSharedFile(), 466 | ); 467 | } 468 | 469 | if (preferences.telegramRelated && !preferences.isMonorepo) 470 | await fs.writeFile(`${projectDir}/src/bot.ts`, getBotFile()); 471 | 472 | setTitle("Template generation is complete!"); 473 | }); 474 | 475 | const commands = getInstallCommands(preferences); 476 | 477 | for await (const command of commands) { 478 | await task(command, async () => { 479 | await exec(command, { 480 | cwd: projectDir, 481 | }).catch((e) => console.error(e)); 482 | }); 483 | } 484 | }); 485 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "configVersion": 1, 4 | "workspaces": { 5 | "": { 6 | "name": "create-elysiajs", 7 | "dependencies": { 8 | "enquirer": "^2.4.1", 9 | "minimist": "^1.2.8", 10 | "tasuku": "^2.0.1", 11 | }, 12 | "devDependencies": { 13 | "@biomejs/biome": "^2.0.0", 14 | "@types/bun": "^1.2.5", 15 | "@types/minimist": "^1.2.5", 16 | "husky": "^9.1.7", 17 | "pkgroll": "^2.11.2", 18 | "ts-dedent": "^2.2.0", 19 | "typescript": "^5.8.2", 20 | }, 21 | }, 22 | }, 23 | "packages": { 24 | "@biomejs/biome": ["@biomejs/biome@2.3.5", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.5", "@biomejs/cli-darwin-x64": "2.3.5", "@biomejs/cli-linux-arm64": "2.3.5", "@biomejs/cli-linux-arm64-musl": "2.3.5", "@biomejs/cli-linux-x64": "2.3.5", "@biomejs/cli-linux-x64-musl": "2.3.5", "@biomejs/cli-win32-arm64": "2.3.5", "@biomejs/cli-win32-x64": "2.3.5" }, "bin": { "biome": "bin/biome" } }, "sha512-HvLhNlIlBIbAV77VysRIBEwp55oM/QAjQEin74QQX9Xb259/XP/D5AGGnZMOyF1el4zcvlNYYR3AyTMUV3ILhg=="], 25 | 26 | "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fLdTur8cJU33HxHUUsii3GLx/TR0BsfQx8FkeqIiW33cGMtUD56fAtrh+2Fx1uhiCsVZlFh6iLKUU3pniZREQw=="], 27 | 28 | "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-qpT8XDqeUlzrOW8zb4k3tjhT7rmvVRumhi2657I2aGcY4B+Ft5fNwDdZGACzn8zj7/K1fdWjgwYE3i2mSZ+vOA=="], 29 | 30 | "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-u/pybjTBPGBHB66ku4pK1gj+Dxgx7/+Z0jAriZISPX1ocTO8aHh8x8e7Kb1rB4Ms0nA/SzjtNOVJ4exVavQBCw=="], 31 | 32 | "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-eGUG7+hcLgGnMNl1KHVZUYxahYAhC462jF/wQolqu4qso2MSk32Q+QrpN7eN4jAHAg7FUMIo897muIhK4hXhqg=="], 33 | 34 | "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-XrIVi9YAW6ye0CGQ+yax0gLfx+BFOtKaNX74n+xHWla6Cl6huUmcKNO7HPx7BiKnJUzrxXY1qYlm7xMvi08X4g=="], 35 | 36 | "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.5", "", { "os": "linux", "cpu": "x64" }, "sha512-awVuycTPpVTH/+WDVnEEYSf6nbCBHf/4wB3lquwT7puhNg8R4XvonWNZzUsfHZrCkjkLhFH/vCZK5jHatD9FEg=="], 37 | 38 | "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-DlBiMlBZZ9eIq4H7RimDSGsYcOtfOIfZOaI5CqsWiSlbTfqbPVfWtCf92wNzx8GNMbu1s7/g3ZZESr6+GwM/SA=="], 39 | 40 | "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.5", "", { "os": "win32", "cpu": "x64" }, "sha512-nUmR8gb6yvrKhtRgzwo/gDimPwnO5a4sCydf8ZS2kHIJhEmSmk+STsusr1LHTuM//wXppBawvSQi2xFXJCdgKQ=="], 41 | 42 | "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], 43 | 44 | "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], 45 | 46 | "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], 47 | 48 | "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], 49 | 50 | "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], 51 | 52 | "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], 53 | 54 | "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], 55 | 56 | "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], 57 | 58 | "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], 59 | 60 | "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], 61 | 62 | "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], 63 | 64 | "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], 65 | 66 | "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], 67 | 68 | "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], 69 | 70 | "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], 71 | 72 | "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], 73 | 74 | "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], 75 | 76 | "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], 77 | 78 | "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], 79 | 80 | "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], 81 | 82 | "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], 83 | 84 | "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], 85 | 86 | "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], 87 | 88 | "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], 89 | 90 | "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], 91 | 92 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], 93 | 94 | "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 95 | 96 | "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 97 | 98 | "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 99 | 100 | "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 101 | 102 | "@rollup/plugin-alias": ["@rollup/plugin-alias@5.1.1", "", { "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ=="], 103 | 104 | "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.9", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA=="], 105 | 106 | "@rollup/plugin-dynamic-import-vars": ["@rollup/plugin-dynamic-import-vars@2.1.5", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "astring": "^1.8.5", "estree-walker": "^2.0.2", "fast-glob": "^3.2.12", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-Mymi24fd9hlRifdZV/jYIFj1dn99F34imiYu3KzlAcgBcRi3i9SucgW/VRo5SQ9K4NuQ7dCep6pFWgNyhRdFHQ=="], 107 | 108 | "@rollup/plugin-inject": ["@rollup/plugin-inject@5.0.5", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "estree-walker": "^2.0.2", "magic-string": "^0.30.3" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg=="], 109 | 110 | "@rollup/plugin-json": ["@rollup/plugin-json@6.1.0", "", { "dependencies": { "@rollup/pluginutils": "^5.1.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA=="], 111 | 112 | "@rollup/plugin-node-resolve": ["@rollup/plugin-node-resolve@16.0.3", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "@types/resolve": "1.20.2", "deepmerge": "^4.2.2", "is-module": "^1.0.0", "resolve": "^1.22.1" }, "peerDependencies": { "rollup": "^2.78.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg=="], 113 | 114 | "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], 115 | 116 | "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.2", "", { "os": "android", "cpu": "arm" }, "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA=="], 117 | 118 | "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.2", "", { "os": "android", "cpu": "arm64" }, "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g=="], 119 | 120 | "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ=="], 121 | 122 | "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw=="], 123 | 124 | "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA=="], 125 | 126 | "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA=="], 127 | 128 | "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg=="], 129 | 130 | "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q=="], 131 | 132 | "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA=="], 133 | 134 | "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ=="], 135 | 136 | "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ=="], 137 | 138 | "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g=="], 139 | 140 | "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA=="], 141 | 142 | "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ=="], 143 | 144 | "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w=="], 145 | 146 | "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw=="], 147 | 148 | "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA=="], 149 | 150 | "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.2", "", { "os": "none", "cpu": "arm64" }, "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A=="], 151 | 152 | "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA=="], 153 | 154 | "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg=="], 155 | 156 | "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw=="], 157 | 158 | "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA=="], 159 | 160 | "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], 161 | 162 | "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 163 | 164 | "@types/minimist": ["@types/minimist@1.2.5", "", {}, "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag=="], 165 | 166 | "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], 167 | 168 | "@types/react": ["@types/react@19.2.4", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-tBFxBp9Nfyy5rsmefN+WXc1JeW/j2BpBHFdLZbEVfs9wn3E3NRFxwV0pJg8M1qQAexFpvz73hJXFofV0ZAu92A=="], 169 | 170 | "@types/resolve": ["@types/resolve@1.20.2", "", {}, "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="], 171 | 172 | "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], 173 | 174 | "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 175 | 176 | "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], 177 | 178 | "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 179 | 180 | "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], 181 | 182 | "cjs-module-lexer": ["cjs-module-lexer@2.1.1", "", {}, "sha512-+CmxIZ/L2vNcEfvNtLdU0ZQ6mbq3FZnwAP2PPTiKP+1QOoKwlKlPgb8UKV0Dds7QVaMnHm+FwSft2VB0s/SLjQ=="], 183 | 184 | "commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="], 185 | 186 | "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], 187 | 188 | "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], 189 | 190 | "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], 191 | 192 | "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], 193 | 194 | "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], 195 | 196 | "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], 197 | 198 | "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], 199 | 200 | "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 201 | 202 | "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 203 | 204 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 205 | 206 | "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 207 | 208 | "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 209 | 210 | "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 211 | 212 | "husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="], 213 | 214 | "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], 215 | 216 | "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 217 | 218 | "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 219 | 220 | "is-module": ["is-module@1.0.0", "", {}, "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="], 221 | 222 | "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 223 | 224 | "is-reference": ["is-reference@1.2.1", "", { "dependencies": { "@types/estree": "*" } }, "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ=="], 225 | 226 | "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 227 | 228 | "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 229 | 230 | "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 231 | 232 | "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 233 | 234 | "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], 235 | 236 | "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 237 | 238 | "pkgroll": ["pkgroll@2.20.1", "", { "dependencies": { "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-dynamic-import-vars": "^2.1.5", "@rollup/plugin-inject": "^5.0.5", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.1", "@rollup/pluginutils": "^5.2.0", "cjs-module-lexer": "^2.1.0", "esbuild": "^0.25.8", "magic-string": "^0.30.17", "rollup": "^4.46.2", "rollup-pluginutils": "^2.8.2", "yaml": "^2.8.1" }, "peerDependencies": { "typescript": "^4.1 || ^5.0" }, "optionalPeers": ["typescript"], "bin": { "pkgroll": "dist/cli.mjs" } }, "sha512-+ljnIUQDxOgceLmtM7P3jj88GGk6P8YMqT/EkwKQXIgnsAX7W4LhcMQXQlWYfDE9Rg08k+57FG1omqaeT8+gew=="], 239 | 240 | "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 241 | 242 | "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], 243 | 244 | "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 245 | 246 | "rollup": ["rollup@4.53.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.2", "@rollup/rollup-android-arm64": "4.53.2", "@rollup/rollup-darwin-arm64": "4.53.2", "@rollup/rollup-darwin-x64": "4.53.2", "@rollup/rollup-freebsd-arm64": "4.53.2", "@rollup/rollup-freebsd-x64": "4.53.2", "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", "@rollup/rollup-linux-arm-musleabihf": "4.53.2", "@rollup/rollup-linux-arm64-gnu": "4.53.2", "@rollup/rollup-linux-arm64-musl": "4.53.2", "@rollup/rollup-linux-loong64-gnu": "4.53.2", "@rollup/rollup-linux-ppc64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-musl": "4.53.2", "@rollup/rollup-linux-s390x-gnu": "4.53.2", "@rollup/rollup-linux-x64-gnu": "4.53.2", "@rollup/rollup-linux-x64-musl": "4.53.2", "@rollup/rollup-openharmony-arm64": "4.53.2", "@rollup/rollup-win32-arm64-msvc": "4.53.2", "@rollup/rollup-win32-ia32-msvc": "4.53.2", "@rollup/rollup-win32-x64-gnu": "4.53.2", "@rollup/rollup-win32-x64-msvc": "4.53.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g=="], 247 | 248 | "rollup-pluginutils": ["rollup-pluginutils@2.8.2", "", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="], 249 | 250 | "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 251 | 252 | "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 253 | 254 | "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 255 | 256 | "tasuku": ["tasuku@2.0.2", "", {}, "sha512-7WT61fFiiMPvcXqcECaO2UH17VugTwLsyPbK2dEK8UgeI/WntNj4fmbbRpJ/l81MW+Le/uyRUTKchZG6qpvdFA=="], 257 | 258 | "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 259 | 260 | "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], 261 | 262 | "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 263 | 264 | "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 265 | 266 | "yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], 267 | 268 | "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 269 | 270 | "rollup-pluginutils/estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], 271 | } 272 | } 273 | --------------------------------------------------------------------------------