Add backend framework selection between hono, elysiajs

This commit is contained in:
Aman Varshney
2025-03-26 11:41:41 +05:30
parent b6b113766e
commit 91fe9f861f
40 changed files with 451 additions and 345 deletions

View File

@@ -0,0 +1,41 @@
import path from "node:path";
import type { AvailableDependencies } from "../constants";
import type { BackendFramework, Runtime } from "../types";
import { addPackageDependency } from "../utils/add-package-deps";
export async function setupBackendDependencies(
projectDir: string,
framework: BackendFramework,
runtime: Runtime,
): Promise<void> {
const serverDir = path.join(projectDir, "apps/server");
const dependencies: AvailableDependencies[] = [];
const devDependencies: AvailableDependencies[] = [];
if (framework === "hono") {
dependencies.push("hono", "@hono/trpc-server");
if (runtime === "node") {
dependencies.push("@hono/node-server");
devDependencies.push("tsx", "@types/node");
}
} else if (framework === "elysia") {
dependencies.push("elysia", "@elysiajs/cors", "@elysiajs/trpc");
if (runtime === "node") {
dependencies.push("@elysiajs/node");
devDependencies.push("tsx", "@types/node");
}
}
if (runtime === "bun") {
devDependencies.push("@types/bun");
}
addPackageDependency({
dependencies,
devDependencies,
projectDir: serverDir,
});
}

View File

@@ -5,6 +5,7 @@ import pc from "picocolors";
import type { ProjectConfig } from "../types";
import { setupAddons } from "./addons-setup";
import { setupAuth } from "./auth-setup";
import { setupBackendDependencies } from "./backend-framework-setup";
import { createReadme } from "./create-readme";
import { setupDatabase } from "./db-setup";
import { setupEnvironmentVariables } from "./env-setup";
@@ -16,6 +17,7 @@ import {
copyBaseTemplate,
fixGitignoreFiles,
setupAuthTemplate,
setupBackendFramework,
setupOrmTemplate,
} from "./template-manager";
@@ -27,10 +29,14 @@ export async function createProject(options: ProjectConfig): Promise<string> {
await fs.ensureDir(projectDir);
await copyBaseTemplate(projectDir);
await fixGitignoreFiles(projectDir);
await setupAuthTemplate(projectDir, options.auth);
await setupBackendFramework(projectDir, options.backendFramework);
await setupBackendDependencies(
projectDir,
options.backendFramework,
options.runtime,
);
await setupOrmTemplate(
projectDir,
@@ -39,15 +45,6 @@ export async function createProject(options: ProjectConfig): Promise<string> {
options.auth,
);
await setupRuntime(projectDir, options.runtime);
await setupExamples(
projectDir,
options.examples,
options.orm,
options.auth,
);
await setupDatabase(
projectDir,
options.database,
@@ -55,8 +52,24 @@ export async function createProject(options: ProjectConfig): Promise<string> {
options.turso ?? options.database === "sqlite",
);
await setupAuthTemplate(
projectDir,
options.auth,
options.backendFramework,
options.orm,
options.database,
);
await setupAuth(projectDir, options.auth);
await setupRuntime(projectDir, options.runtime, options.backendFramework);
await setupExamples(
projectDir,
options.examples,
options.orm,
options.auth,
);
await setupEnvironmentVariables(projectDir, options);
await initializeGit(projectDir, options.git);
@@ -66,7 +79,6 @@ export async function createProject(options: ProjectConfig): Promise<string> {
}
await updatePackageConfigurations(projectDir, options);
await createReadme(projectDir, options);
displayPostInstallInstructions(

View File

@@ -33,9 +33,7 @@ export function displayPostInstallInstructions(
? getLintingInstructions(runCmd)
: "";
log.info(`${pc.cyan("Project created successfully!")}
${pc.bold("Next steps:")}
log.info(`${pc.bold("Next steps:")}
${pc.cyan("1.")} ${cdCmd}
${!depsInstalled ? `${pc.cyan("2.")} ${packageManager} install\n` : ""}${pc.cyan(depsInstalled ? "2." : "3.")} ${runCmd} dev

View File

@@ -1,11 +1,12 @@
import path from "node:path";
import fs from "fs-extra";
import type { Runtime } from "../types";
import type { BackendFramework, Runtime } from "../types";
import { addPackageDependency } from "../utils/add-package-deps";
export async function setupRuntime(
projectDir: string,
runtime: Runtime,
backendFramework: BackendFramework,
): Promise<void> {
const serverDir = path.join(projectDir, "apps/server");
const serverIndexPath = path.join(serverDir, "src/index.ts");
@@ -13,9 +14,19 @@ export async function setupRuntime(
const indexContent = await fs.readFile(serverIndexPath, "utf-8");
if (runtime === "bun") {
await setupBunRuntime(serverDir, serverIndexPath, indexContent);
await setupBunRuntime(
serverDir,
serverIndexPath,
indexContent,
backendFramework,
);
} else if (runtime === "node") {
await setupNodeRuntime(serverDir, serverIndexPath, indexContent);
await setupNodeRuntime(
serverDir,
serverIndexPath,
indexContent,
backendFramework,
);
}
}
@@ -23,6 +34,7 @@ async function setupBunRuntime(
serverDir: string,
serverIndexPath: string,
indexContent: string,
backendFramework: BackendFramework,
): Promise<void> {
const packageJsonPath = path.join(serverDir, "package.json");
const packageJson = await fs.readJson(packageJsonPath);
@@ -40,7 +52,7 @@ async function setupBunRuntime(
projectDir: serverDir,
});
if (!indexContent.includes("export default app")) {
if (backendFramework === "hono") {
const updatedContent = `${indexContent}\n\nexport default app;\n`;
await fs.writeFile(serverIndexPath, updatedContent);
}
@@ -50,13 +62,8 @@ async function setupNodeRuntime(
serverDir: string,
serverIndexPath: string,
indexContent: string,
backendFramework: BackendFramework,
): Promise<void> {
addPackageDependency({
dependencies: ["@hono/node-server"],
devDependencies: ["tsx", "@types/node"],
projectDir: serverDir,
});
const packageJsonPath = path.join(serverDir, "package.json");
const packageJson = await fs.readJson(packageJsonPath);
@@ -68,27 +75,62 @@ async function setupNodeRuntime(
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
const importLine = 'import { serve } from "@hono/node-server";\n';
const serverCode = `
addPackageDependency({
devDependencies: ["tsx", "@types/node"],
projectDir: serverDir,
});
if (backendFramework === "hono") {
addPackageDependency({
dependencies: ["@hono/node-server"],
projectDir: serverDir,
});
const importLine = 'import { serve } from "@hono/node-server";\n';
const serverCode = `
serve(
{
fetch: app.fetch,
port: 3000,
},
(info) => {
console.log(\`Server is running on http://localhost:\${info.port}\`);
},
{
fetch: app.fetch,
port: 3000,
},
(info) => {
console.log(\`Server is running on http://localhost:\${info.port}\`);
},
);\n`;
if (!indexContent.includes("@hono/node-server")) {
const importEndIndex = indexContent.lastIndexOf("import");
const importSection = indexContent.substring(0, importEndIndex);
const restOfFile = indexContent.substring(importEndIndex);
if (!indexContent.includes("@hono/node-server")) {
const importEndIndex = indexContent.lastIndexOf("import");
const importSection = indexContent.substring(0, importEndIndex);
const restOfFile = indexContent.substring(importEndIndex);
const updatedContent = importSection + importLine + restOfFile + serverCode;
await fs.writeFile(serverIndexPath, updatedContent);
} else if (!indexContent.includes("serve(")) {
const updatedContent = indexContent + serverCode;
await fs.writeFile(serverIndexPath, updatedContent);
const updatedContent =
importSection + importLine + restOfFile + serverCode;
await fs.writeFile(serverIndexPath, updatedContent);
}
} else if (backendFramework === "elysia") {
addPackageDependency({
dependencies: ["@elysiajs/node"],
projectDir: serverDir,
});
if (!indexContent.includes("@elysiajs/node")) {
const nodeImport = 'import { node } from "@elysiajs/node";\n';
const firstImportEnd = indexContent.indexOf(
"\n",
indexContent.indexOf("import"),
);
const before = indexContent.substring(0, firstImportEnd + 1);
const after = indexContent.substring(firstImportEnd + 1);
let updatedContent = before + nodeImport + after;
updatedContent = updatedContent.replace(
/const app = new Elysia\([^)]*\)/,
"const app = new Elysia({ adapter: node() })",
);
await fs.writeFile(serverIndexPath, updatedContent);
}
}
}

View File

@@ -1,7 +1,7 @@
import path from "node:path";
import fs from "fs-extra";
import { PKG_ROOT } from "../constants";
import type { ProjectDatabase, ProjectOrm } from "../types";
import type { BackendFramework, ProjectDatabase, ProjectOrm } from "../types";
export async function copyBaseTemplate(projectDir: string): Promise<void> {
const templateDir = path.join(PKG_ROOT, "template/base");
@@ -11,15 +11,13 @@ export async function copyBaseTemplate(projectDir: string): Promise<void> {
await fs.copy(templateDir, projectDir);
}
export async function setupAuthTemplate(
export async function setupBackendFramework(
projectDir: string,
auth: boolean,
framework: BackendFramework,
): Promise<void> {
if (!auth) return;
const authTemplateDir = path.join(PKG_ROOT, "template/with-auth");
if (await fs.pathExists(authTemplateDir)) {
await fs.copy(authTemplateDir, projectDir, { overwrite: true });
const frameworkDir = path.join(PKG_ROOT, `template/with-${framework}`);
if (await fs.pathExists(frameworkDir)) {
await fs.copy(frameworkDir, projectDir, { overwrite: true });
}
}
@@ -36,17 +34,83 @@ export async function setupOrmTemplate(
if (await fs.pathExists(ormTemplateDir)) {
await fs.copy(ormTemplateDir, projectDir, { overwrite: true });
const serverSrcPath = path.join(projectDir, "apps/server/src");
const libPath = path.join(serverSrcPath, "lib");
const withAuthLibPath = path.join(serverSrcPath, "with-auth-lib");
if (auth) {
if (await fs.pathExists(withAuthLibPath)) {
await fs.remove(libPath);
await fs.move(withAuthLibPath, libPath);
if (!auth) {
if (orm === "prisma") {
const authSchemaPath = path.join(
projectDir,
"apps/server/prisma/schema/auth.prisma",
);
if (await fs.pathExists(authSchemaPath)) {
await fs.remove(authSchemaPath);
}
} else if (orm === "drizzle") {
const authSchemaPath = path.join(
projectDir,
"apps/server/src/db/schema/auth.ts",
);
if (await fs.pathExists(authSchemaPath)) {
await fs.remove(authSchemaPath);
}
}
}
}
}
export async function setupAuthTemplate(
projectDir: string,
auth: boolean,
framework: BackendFramework,
orm: ProjectOrm,
database: ProjectDatabase,
): Promise<void> {
if (!auth) return;
const authTemplateDir = path.join(PKG_ROOT, "template/with-auth");
if (await fs.pathExists(authTemplateDir)) {
const clientAuthDir = path.join(authTemplateDir, "apps/client");
const projectClientDir = path.join(projectDir, "apps/client");
await fs.copy(clientAuthDir, projectClientDir, { overwrite: true });
const serverAuthDir = path.join(authTemplateDir, "apps/server/src");
const projectServerDir = path.join(projectDir, "apps/server/src");
await fs.copy(
path.join(serverAuthDir, "lib/trpc.ts"),
path.join(projectServerDir, "lib/trpc.ts"),
{ overwrite: true },
);
await fs.copy(
path.join(serverAuthDir, "routers/index.ts"),
path.join(projectServerDir, "routers/index.ts"),
{ overwrite: true },
);
const contextFileName = `with-${framework}-context.ts`;
await fs.copy(
path.join(serverAuthDir, "lib", contextFileName),
path.join(projectServerDir, "lib/context.ts"),
{ overwrite: true },
);
const indexFileName = `with-${framework}-index.ts`;
await fs.copy(
path.join(serverAuthDir, indexFileName),
path.join(projectServerDir, "index.ts"),
{ overwrite: true },
);
const authLibFileName = getAuthLibDir(orm, database);
const authLibSourceDir = path.join(serverAuthDir, authLibFileName);
if (await fs.pathExists(authLibSourceDir)) {
const files = await fs.readdir(authLibSourceDir);
for (const file of files) {
await fs.copy(
path.join(authLibSourceDir, file),
path.join(projectServerDir, "lib", file),
{ overwrite: true },
);
}
} else {
await fs.remove(withAuthLibPath);
}
}
}
@@ -81,3 +145,19 @@ function getOrmTemplateDir(orm: ProjectOrm, database: ProjectDatabase): string {
return "template/base";
}
function getAuthLibDir(orm: ProjectOrm, database: ProjectDatabase): string {
if (orm === "drizzle") {
return database === "sqlite"
? "with-drizzle-sqlite-lib"
: "with-drizzle-postgres-lib";
}
if (orm === "prisma") {
return database === "sqlite"
? "with-prisma-sqlite-lib"
: "with-prisma-postgres-lib";
}
throw new Error("Invalid ORM or database configuration for auth setup");
}