mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Add backend framework selection between hono, elysiajs
This commit is contained in:
5
.changeset/fruity-melons-argue.md
Normal file
5
.changeset/fruity-melons-argue.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add option to choose elysiajs as backend framework
|
||||||
@@ -7,10 +7,7 @@
|
|||||||
"bin": {
|
"bin": {
|
||||||
"create-better-t-stack": "dist/index.js"
|
"create-better-t-stack": "dist/index.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": ["dist", "template"],
|
||||||
"dist",
|
|
||||||
"template"
|
|
||||||
],
|
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import type { ProjectConfig } from "./types";
|
import type { ProjectConfig } from "./types";
|
||||||
|
import { getUserPkgManager } from "./utils/get-package-manager";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const distPath = path.dirname(__filename);
|
const distPath = path.dirname(__filename);
|
||||||
@@ -14,7 +15,7 @@ export const DEFAULT_CONFIG: ProjectConfig = {
|
|||||||
addons: [],
|
addons: [],
|
||||||
examples: [],
|
examples: [],
|
||||||
git: true,
|
git: true,
|
||||||
packageManager: "npm",
|
packageManager: getUserPkgManager(),
|
||||||
noInstall: false,
|
noInstall: false,
|
||||||
turso: false,
|
turso: false,
|
||||||
backendFramework: "hono",
|
backendFramework: "hono",
|
||||||
@@ -48,6 +49,15 @@ export const dependencyVersionMap = {
|
|||||||
"@types/node": "^22.13.11",
|
"@types/node": "^22.13.11",
|
||||||
|
|
||||||
"@types/bun": "^1.2.6",
|
"@types/bun": "^1.2.6",
|
||||||
|
|
||||||
|
"@elysiajs/node": "^1.2.6",
|
||||||
|
|
||||||
|
"@elysiajs/cors": "^1.2.0",
|
||||||
|
"@elysiajs/trpc": "^1.1.0",
|
||||||
|
elysia: "^1.2.25",
|
||||||
|
|
||||||
|
"@hono/trpc-server": "^0.3.4",
|
||||||
|
hono: "^4.7.5",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type AvailableDependencies = keyof typeof dependencyVersionMap;
|
export type AvailableDependencies = keyof typeof dependencyVersionMap;
|
||||||
|
|||||||
41
apps/cli/src/helpers/backend-framework-setup.ts
Normal file
41
apps/cli/src/helpers/backend-framework-setup.ts
Normal 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,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import pc from "picocolors";
|
|||||||
import type { ProjectConfig } from "../types";
|
import type { ProjectConfig } from "../types";
|
||||||
import { setupAddons } from "./addons-setup";
|
import { setupAddons } from "./addons-setup";
|
||||||
import { setupAuth } from "./auth-setup";
|
import { setupAuth } from "./auth-setup";
|
||||||
|
import { setupBackendDependencies } from "./backend-framework-setup";
|
||||||
import { createReadme } from "./create-readme";
|
import { createReadme } from "./create-readme";
|
||||||
import { setupDatabase } from "./db-setup";
|
import { setupDatabase } from "./db-setup";
|
||||||
import { setupEnvironmentVariables } from "./env-setup";
|
import { setupEnvironmentVariables } from "./env-setup";
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
copyBaseTemplate,
|
copyBaseTemplate,
|
||||||
fixGitignoreFiles,
|
fixGitignoreFiles,
|
||||||
setupAuthTemplate,
|
setupAuthTemplate,
|
||||||
|
setupBackendFramework,
|
||||||
setupOrmTemplate,
|
setupOrmTemplate,
|
||||||
} from "./template-manager";
|
} from "./template-manager";
|
||||||
|
|
||||||
@@ -27,10 +29,14 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
await fs.ensureDir(projectDir);
|
await fs.ensureDir(projectDir);
|
||||||
|
|
||||||
await copyBaseTemplate(projectDir);
|
await copyBaseTemplate(projectDir);
|
||||||
|
|
||||||
await fixGitignoreFiles(projectDir);
|
await fixGitignoreFiles(projectDir);
|
||||||
|
|
||||||
await setupAuthTemplate(projectDir, options.auth);
|
await setupBackendFramework(projectDir, options.backendFramework);
|
||||||
|
await setupBackendDependencies(
|
||||||
|
projectDir,
|
||||||
|
options.backendFramework,
|
||||||
|
options.runtime,
|
||||||
|
);
|
||||||
|
|
||||||
await setupOrmTemplate(
|
await setupOrmTemplate(
|
||||||
projectDir,
|
projectDir,
|
||||||
@@ -39,15 +45,6 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
options.auth,
|
options.auth,
|
||||||
);
|
);
|
||||||
|
|
||||||
await setupRuntime(projectDir, options.runtime);
|
|
||||||
|
|
||||||
await setupExamples(
|
|
||||||
projectDir,
|
|
||||||
options.examples,
|
|
||||||
options.orm,
|
|
||||||
options.auth,
|
|
||||||
);
|
|
||||||
|
|
||||||
await setupDatabase(
|
await setupDatabase(
|
||||||
projectDir,
|
projectDir,
|
||||||
options.database,
|
options.database,
|
||||||
@@ -55,8 +52,24 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
options.turso ?? options.database === "sqlite",
|
options.turso ?? options.database === "sqlite",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await setupAuthTemplate(
|
||||||
|
projectDir,
|
||||||
|
options.auth,
|
||||||
|
options.backendFramework,
|
||||||
|
options.orm,
|
||||||
|
options.database,
|
||||||
|
);
|
||||||
await setupAuth(projectDir, options.auth);
|
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 setupEnvironmentVariables(projectDir, options);
|
||||||
|
|
||||||
await initializeGit(projectDir, options.git);
|
await initializeGit(projectDir, options.git);
|
||||||
@@ -66,7 +79,6 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await updatePackageConfigurations(projectDir, options);
|
await updatePackageConfigurations(projectDir, options);
|
||||||
|
|
||||||
await createReadme(projectDir, options);
|
await createReadme(projectDir, options);
|
||||||
|
|
||||||
displayPostInstallInstructions(
|
displayPostInstallInstructions(
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ export function displayPostInstallInstructions(
|
|||||||
? getLintingInstructions(runCmd)
|
? getLintingInstructions(runCmd)
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
log.info(`${pc.cyan("Project created successfully!")}
|
log.info(`${pc.bold("Next steps:")}
|
||||||
|
|
||||||
${pc.bold("Next steps:")}
|
|
||||||
${pc.cyan("1.")} ${cdCmd}
|
${pc.cyan("1.")} ${cdCmd}
|
||||||
${!depsInstalled ? `${pc.cyan("2.")} ${packageManager} install\n` : ""}${pc.cyan(depsInstalled ? "2." : "3.")} ${runCmd} dev
|
${!depsInstalled ? `${pc.cyan("2.")} ${packageManager} install\n` : ""}${pc.cyan(depsInstalled ? "2." : "3.")} ${runCmd} dev
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import type { Runtime } from "../types";
|
import type { BackendFramework, Runtime } from "../types";
|
||||||
import { addPackageDependency } from "../utils/add-package-deps";
|
import { addPackageDependency } from "../utils/add-package-deps";
|
||||||
|
|
||||||
export async function setupRuntime(
|
export async function setupRuntime(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
runtime: Runtime,
|
runtime: Runtime,
|
||||||
|
backendFramework: BackendFramework,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const serverDir = path.join(projectDir, "apps/server");
|
const serverDir = path.join(projectDir, "apps/server");
|
||||||
const serverIndexPath = path.join(serverDir, "src/index.ts");
|
const serverIndexPath = path.join(serverDir, "src/index.ts");
|
||||||
@@ -13,9 +14,19 @@ export async function setupRuntime(
|
|||||||
const indexContent = await fs.readFile(serverIndexPath, "utf-8");
|
const indexContent = await fs.readFile(serverIndexPath, "utf-8");
|
||||||
|
|
||||||
if (runtime === "bun") {
|
if (runtime === "bun") {
|
||||||
await setupBunRuntime(serverDir, serverIndexPath, indexContent);
|
await setupBunRuntime(
|
||||||
|
serverDir,
|
||||||
|
serverIndexPath,
|
||||||
|
indexContent,
|
||||||
|
backendFramework,
|
||||||
|
);
|
||||||
} else if (runtime === "node") {
|
} else if (runtime === "node") {
|
||||||
await setupNodeRuntime(serverDir, serverIndexPath, indexContent);
|
await setupNodeRuntime(
|
||||||
|
serverDir,
|
||||||
|
serverIndexPath,
|
||||||
|
indexContent,
|
||||||
|
backendFramework,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,6 +34,7 @@ async function setupBunRuntime(
|
|||||||
serverDir: string,
|
serverDir: string,
|
||||||
serverIndexPath: string,
|
serverIndexPath: string,
|
||||||
indexContent: string,
|
indexContent: string,
|
||||||
|
backendFramework: BackendFramework,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const packageJsonPath = path.join(serverDir, "package.json");
|
const packageJsonPath = path.join(serverDir, "package.json");
|
||||||
const packageJson = await fs.readJson(packageJsonPath);
|
const packageJson = await fs.readJson(packageJsonPath);
|
||||||
@@ -40,7 +52,7 @@ async function setupBunRuntime(
|
|||||||
projectDir: serverDir,
|
projectDir: serverDir,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!indexContent.includes("export default app")) {
|
if (backendFramework === "hono") {
|
||||||
const updatedContent = `${indexContent}\n\nexport default app;\n`;
|
const updatedContent = `${indexContent}\n\nexport default app;\n`;
|
||||||
await fs.writeFile(serverIndexPath, updatedContent);
|
await fs.writeFile(serverIndexPath, updatedContent);
|
||||||
}
|
}
|
||||||
@@ -50,13 +62,8 @@ async function setupNodeRuntime(
|
|||||||
serverDir: string,
|
serverDir: string,
|
||||||
serverIndexPath: string,
|
serverIndexPath: string,
|
||||||
indexContent: string,
|
indexContent: string,
|
||||||
|
backendFramework: BackendFramework,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
addPackageDependency({
|
|
||||||
dependencies: ["@hono/node-server"],
|
|
||||||
devDependencies: ["tsx", "@types/node"],
|
|
||||||
projectDir: serverDir,
|
|
||||||
});
|
|
||||||
|
|
||||||
const packageJsonPath = path.join(serverDir, "package.json");
|
const packageJsonPath = path.join(serverDir, "package.json");
|
||||||
const packageJson = await fs.readJson(packageJsonPath);
|
const packageJson = await fs.readJson(packageJsonPath);
|
||||||
|
|
||||||
@@ -68,27 +75,62 @@ async function setupNodeRuntime(
|
|||||||
|
|
||||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||||
|
|
||||||
const importLine = 'import { serve } from "@hono/node-server";\n';
|
addPackageDependency({
|
||||||
const serverCode = `
|
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(
|
serve(
|
||||||
{
|
{
|
||||||
fetch: app.fetch,
|
fetch: app.fetch,
|
||||||
port: 3000,
|
port: 3000,
|
||||||
},
|
},
|
||||||
(info) => {
|
(info) => {
|
||||||
console.log(\`Server is running on http://localhost:\${info.port}\`);
|
console.log(\`Server is running on http://localhost:\${info.port}\`);
|
||||||
},
|
},
|
||||||
);\n`;
|
);\n`;
|
||||||
|
|
||||||
if (!indexContent.includes("@hono/node-server")) {
|
if (!indexContent.includes("@hono/node-server")) {
|
||||||
const importEndIndex = indexContent.lastIndexOf("import");
|
const importEndIndex = indexContent.lastIndexOf("import");
|
||||||
const importSection = indexContent.substring(0, importEndIndex);
|
const importSection = indexContent.substring(0, importEndIndex);
|
||||||
const restOfFile = indexContent.substring(importEndIndex);
|
const restOfFile = indexContent.substring(importEndIndex);
|
||||||
|
|
||||||
const updatedContent = importSection + importLine + restOfFile + serverCode;
|
const updatedContent =
|
||||||
await fs.writeFile(serverIndexPath, updatedContent);
|
importSection + importLine + restOfFile + serverCode;
|
||||||
} else if (!indexContent.includes("serve(")) {
|
await fs.writeFile(serverIndexPath, updatedContent);
|
||||||
const updatedContent = indexContent + 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import fs from "fs-extra";
|
import fs from "fs-extra";
|
||||||
import { PKG_ROOT } from "../constants";
|
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> {
|
export async function copyBaseTemplate(projectDir: string): Promise<void> {
|
||||||
const templateDir = path.join(PKG_ROOT, "template/base");
|
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);
|
await fs.copy(templateDir, projectDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setupAuthTemplate(
|
export async function setupBackendFramework(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
auth: boolean,
|
framework: BackendFramework,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!auth) return;
|
const frameworkDir = path.join(PKG_ROOT, `template/with-${framework}`);
|
||||||
|
if (await fs.pathExists(frameworkDir)) {
|
||||||
const authTemplateDir = path.join(PKG_ROOT, "template/with-auth");
|
await fs.copy(frameworkDir, projectDir, { overwrite: true });
|
||||||
if (await fs.pathExists(authTemplateDir)) {
|
|
||||||
await fs.copy(authTemplateDir, projectDir, { overwrite: true });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,17 +34,83 @@ export async function setupOrmTemplate(
|
|||||||
if (await fs.pathExists(ormTemplateDir)) {
|
if (await fs.pathExists(ormTemplateDir)) {
|
||||||
await fs.copy(ormTemplateDir, projectDir, { overwrite: true });
|
await fs.copy(ormTemplateDir, projectDir, { overwrite: true });
|
||||||
|
|
||||||
const serverSrcPath = path.join(projectDir, "apps/server/src");
|
if (!auth) {
|
||||||
const libPath = path.join(serverSrcPath, "lib");
|
if (orm === "prisma") {
|
||||||
const withAuthLibPath = path.join(serverSrcPath, "with-auth-lib");
|
const authSchemaPath = path.join(
|
||||||
|
projectDir,
|
||||||
if (auth) {
|
"apps/server/prisma/schema/auth.prisma",
|
||||||
if (await fs.pathExists(withAuthLibPath)) {
|
);
|
||||||
await fs.remove(libPath);
|
if (await fs.pathExists(authSchemaPath)) {
|
||||||
await fs.move(withAuthLibPath, libPath);
|
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";
|
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");
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { createProject } from "./helpers/create-project";
|
|||||||
import { installDependencies } from "./helpers/install-dependencies";
|
import { installDependencies } from "./helpers/install-dependencies";
|
||||||
import { gatherConfig } from "./prompts/config-prompts";
|
import { gatherConfig } from "./prompts/config-prompts";
|
||||||
import type {
|
import type {
|
||||||
|
BackendFramework,
|
||||||
ProjectAddons,
|
ProjectAddons,
|
||||||
ProjectConfig,
|
ProjectConfig,
|
||||||
ProjectExamples,
|
ProjectExamples,
|
||||||
@@ -56,6 +57,7 @@ async function main() {
|
|||||||
.option("--turso", "Set up Turso for SQLite database")
|
.option("--turso", "Set up Turso for SQLite database")
|
||||||
.option("--no-turso", "Skip Turso setup for SQLite database")
|
.option("--no-turso", "Skip Turso setup for SQLite database")
|
||||||
.option("--hono", "Use Hono backend framework")
|
.option("--hono", "Use Hono backend framework")
|
||||||
|
.option("--elysia", "Use Elysia backend framework")
|
||||||
.option("--runtime <runtime>", "Specify runtime (bun or node)")
|
.option("--runtime <runtime>", "Specify runtime (bun or node)")
|
||||||
.parse();
|
.parse();
|
||||||
|
|
||||||
@@ -68,6 +70,10 @@ async function main() {
|
|||||||
const options = program.opts();
|
const options = program.opts();
|
||||||
const projectDirectory = program.args[0];
|
const projectDirectory = program.args[0];
|
||||||
|
|
||||||
|
let backendFramework: BackendFramework | undefined;
|
||||||
|
if (options.hono) backendFramework = "hono";
|
||||||
|
if (options.elysia) backendFramework = "elysia";
|
||||||
|
|
||||||
const flagConfig: Partial<ProjectConfig> = {
|
const flagConfig: Partial<ProjectConfig> = {
|
||||||
...(projectDirectory && { projectName: projectDirectory }),
|
...(projectDirectory && { projectName: projectDirectory }),
|
||||||
...(options.database === false && { database: "none" }),
|
...(options.database === false && { database: "none" }),
|
||||||
@@ -82,7 +88,7 @@ async function main() {
|
|||||||
...("git" in options && { git: options.git }),
|
...("git" in options && { git: options.git }),
|
||||||
...("install" in options && { noInstall: !options.install }),
|
...("install" in options && { noInstall: !options.install }),
|
||||||
...("turso" in options && { turso: options.turso }),
|
...("turso" in options && { turso: options.turso }),
|
||||||
...(options.hono && { backendFramework: "hono" }),
|
...(backendFramework && { backendFramework }),
|
||||||
...(options.runtime && { runtime: options.runtime as Runtime }),
|
...(options.runtime && { runtime: options.runtime as Runtime }),
|
||||||
...((options.pwa ||
|
...((options.pwa ||
|
||||||
options.tauri ||
|
options.tauri ||
|
||||||
@@ -124,7 +130,11 @@ async function main() {
|
|||||||
database:
|
database:
|
||||||
options.database === false
|
options.database === false
|
||||||
? "none"
|
? "none"
|
||||||
: (options.database ?? DEFAULT_CONFIG.database),
|
: options.sqlite
|
||||||
|
? "sqlite"
|
||||||
|
: options.postgres
|
||||||
|
? "postgres"
|
||||||
|
: DEFAULT_CONFIG.database,
|
||||||
orm:
|
orm:
|
||||||
options.database === false
|
options.database === false
|
||||||
? "none"
|
? "none"
|
||||||
@@ -133,12 +143,10 @@ async function main() {
|
|||||||
: options.prisma
|
: options.prisma
|
||||||
? "prisma"
|
? "prisma"
|
||||||
: DEFAULT_CONFIG.orm,
|
: DEFAULT_CONFIG.orm,
|
||||||
auth: options.auth ?? DEFAULT_CONFIG.auth,
|
auth: "auth" in options ? options.auth : DEFAULT_CONFIG.auth,
|
||||||
git: options.git ?? DEFAULT_CONFIG.git,
|
git: "git" in options ? options.git : DEFAULT_CONFIG.git,
|
||||||
noInstall:
|
noInstall:
|
||||||
"noInstall" in options
|
"install" in options ? !options.install : DEFAULT_CONFIG.noInstall,
|
||||||
? options.noInstall
|
|
||||||
: DEFAULT_CONFIG.noInstall,
|
|
||||||
packageManager:
|
packageManager:
|
||||||
flagConfig.packageManager ?? DEFAULT_CONFIG.packageManager,
|
flagConfig.packageManager ?? DEFAULT_CONFIG.packageManager,
|
||||||
addons: flagConfig.addons?.length
|
addons: flagConfig.addons?.length
|
||||||
@@ -153,9 +161,7 @@ async function main() {
|
|||||||
: flagConfig.database === "sqlite"
|
: flagConfig.database === "sqlite"
|
||||||
? DEFAULT_CONFIG.turso
|
? DEFAULT_CONFIG.turso
|
||||||
: false,
|
: false,
|
||||||
backendFramework: options.hono
|
backendFramework: backendFramework ?? DEFAULT_CONFIG.backendFramework,
|
||||||
? "hono"
|
|
||||||
: DEFAULT_CONFIG.backendFramework,
|
|
||||||
runtime: options.runtime
|
runtime: options.runtime
|
||||||
? (options.runtime as Runtime)
|
? (options.runtime as Runtime)
|
||||||
: DEFAULT_CONFIG.runtime,
|
: DEFAULT_CONFIG.runtime,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cancel, isCancel, multiselect } from "@clack/prompts";
|
import { cancel, isCancel, multiselect } from "@clack/prompts";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
import type { ProjectAddons } from "../types";
|
import type { ProjectAddons } from "../types";
|
||||||
|
|
||||||
export async function getAddonsChoice(
|
export async function getAddonsChoice(
|
||||||
@@ -31,6 +32,7 @@ export async function getAddonsChoice(
|
|||||||
hint: "Add Git hooks with Husky, lint-staged (requires Biome)",
|
hint: "Add Git hooks with Husky, lint-staged (requires Biome)",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
initialValues: DEFAULT_CONFIG.addons,
|
||||||
required: false,
|
required: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// import { cancel, isCancel, select } from "@clack/prompts";
|
import { cancel, isCancel, select } from "@clack/prompts";
|
||||||
// import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
import type { BackendFramework } from "../types";
|
import type { BackendFramework } from "../types";
|
||||||
|
|
||||||
export async function getBackendFrameworkChoice(
|
export async function getBackendFrameworkChoice(
|
||||||
@@ -7,24 +8,27 @@ export async function getBackendFrameworkChoice(
|
|||||||
): Promise<BackendFramework> {
|
): Promise<BackendFramework> {
|
||||||
if (backendFramework !== undefined) return backendFramework;
|
if (backendFramework !== undefined) return backendFramework;
|
||||||
|
|
||||||
return "hono";
|
const response = await select<BackendFramework>({
|
||||||
|
message: "Which backend framework would you like to use?",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: "hono",
|
||||||
|
label: "Hono",
|
||||||
|
hint: "Lightweight, ultrafast web framework",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "elysia",
|
||||||
|
label: "Elysia",
|
||||||
|
hint: "TypeScript framework with end-to-end type safety)",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initialValue: DEFAULT_CONFIG.backendFramework,
|
||||||
|
});
|
||||||
|
|
||||||
// const response = await select<BackendFramework>({
|
if (isCancel(response)) {
|
||||||
// message: "Which backend framework would you like to use?",
|
cancel(pc.red("Operation cancelled"));
|
||||||
// options: [
|
process.exit(0);
|
||||||
// {
|
}
|
||||||
// value: "hono",
|
|
||||||
// label: "Hono",
|
|
||||||
// hint: "Lightweight, ultrafast web framework",
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
// initialValue: "hono",
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (isCancel(response)) {
|
return response;
|
||||||
// cancel(pc.red("Operation cancelled"));
|
|
||||||
// process.exit(0);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return response;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ export async function gatherConfig(
|
|||||||
projectName: async () => {
|
projectName: async () => {
|
||||||
return getProjectName(flags.projectName);
|
return getProjectName(flags.projectName);
|
||||||
},
|
},
|
||||||
runtime: () => getRuntimeChoice(flags.runtime),
|
|
||||||
backendFramework: () => getBackendFrameworkChoice(flags.backendFramework),
|
backendFramework: () => getBackendFrameworkChoice(flags.backendFramework),
|
||||||
|
runtime: () => getRuntimeChoice(flags.runtime),
|
||||||
database: () => getDatabaseChoice(flags.database),
|
database: () => getDatabaseChoice(flags.database),
|
||||||
orm: ({ results }) =>
|
orm: ({ results }) =>
|
||||||
getORMChoice(flags.orm, results.database !== "none"),
|
getORMChoice(flags.orm, results.database !== "none"),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cancel, isCancel, select } from "@clack/prompts";
|
import { cancel, isCancel, select } from "@clack/prompts";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
import type { ProjectDatabase } from "../types";
|
import type { ProjectDatabase } from "../types";
|
||||||
|
|
||||||
export async function getDatabaseChoice(
|
export async function getDatabaseChoice(
|
||||||
@@ -26,7 +27,7 @@ export async function getDatabaseChoice(
|
|||||||
hint: "Traditional relational database",
|
hint: "Traditional relational database",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
initialValue: "sqlite",
|
initialValue: DEFAULT_CONFIG.database,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCancel(response)) {
|
if (isCancel(response)) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cancel, isCancel, select } from "@clack/prompts";
|
import { cancel, isCancel, select } from "@clack/prompts";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
import type { ProjectOrm } from "../types";
|
import type { ProjectOrm } from "../types";
|
||||||
|
|
||||||
export async function getORMChoice(
|
export async function getORMChoice(
|
||||||
@@ -23,7 +24,7 @@ export async function getORMChoice(
|
|||||||
hint: "Powerful, feature-rich ORM with schema migrations",
|
hint: "Powerful, feature-rich ORM with schema migrations",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
initialValue: "drizzle",
|
initialValue: DEFAULT_CONFIG.orm,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCancel(response)) {
|
if (isCancel(response)) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { cancel, isCancel, select } from "@clack/prompts";
|
import { cancel, isCancel, select } from "@clack/prompts";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
import type { Runtime } from "../types";
|
import type { Runtime } from "../types";
|
||||||
|
|
||||||
export async function getRuntimeChoice(runtime?: Runtime): Promise<Runtime> {
|
export async function getRuntimeChoice(runtime?: Runtime): Promise<Runtime> {
|
||||||
@@ -19,7 +20,7 @@ export async function getRuntimeChoice(runtime?: Runtime): Promise<Runtime> {
|
|||||||
hint: "Traditional Node.js runtime",
|
hint: "Traditional Node.js runtime",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
initialValue: "bun",
|
initialValue: DEFAULT_CONFIG.runtime,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCancel(response)) {
|
if (isCancel(response)) {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { cancel, confirm, isCancel } from "@clack/prompts";
|
import { cancel, confirm, isCancel } from "@clack/prompts";
|
||||||
import pc from "picocolors";
|
import pc from "picocolors";
|
||||||
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
|
|
||||||
export async function getTursoSetupChoice(turso?: boolean): Promise<boolean> {
|
export async function getTursoSetupChoice(turso?: boolean): Promise<boolean> {
|
||||||
if (turso !== undefined) return turso;
|
if (turso !== undefined) return turso;
|
||||||
|
|
||||||
const response = await confirm({
|
const response = await confirm({
|
||||||
message: "Set up a Turso database for this project?",
|
message: "Set up a Turso database for this project?",
|
||||||
initialValue: true,
|
initialValue: DEFAULT_CONFIG.turso,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCancel(response)) {
|
if (isCancel(response)) {
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
export type ProjectDatabase = "sqlite" | "postgres" | "none";
|
export type ProjectDatabase = "sqlite" | "postgres" | "none";
|
||||||
export type ProjectOrm = "drizzle" | "prisma" | "none";
|
export type ProjectOrm = "drizzle" | "prisma" | "none";
|
||||||
export type PackageManager = "npm" | "pnpm" | "bun";
|
export type PackageManager = "npm" | "pnpm" | "bun";
|
||||||
export type ProjectAddons = "pwa" | "tauri" | "biome" | "husky";
|
export type ProjectAddons = "pwa" | "biome" | "tauri" | "husky";
|
||||||
|
export type BackendFramework = "hono" | "elysia";
|
||||||
|
export type Runtime = "node" | "bun";
|
||||||
export type ProjectExamples = "todo";
|
export type ProjectExamples = "todo";
|
||||||
export type BackendFramework = "hono";
|
|
||||||
export type Runtime = "bun" | "node";
|
|
||||||
|
|
||||||
export interface ProjectConfig {
|
export interface ProjectConfig {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
|
backendFramework: BackendFramework;
|
||||||
|
runtime: Runtime;
|
||||||
database: ProjectDatabase;
|
database: ProjectDatabase;
|
||||||
orm: ProjectOrm;
|
orm: ProjectOrm;
|
||||||
auth: boolean;
|
auth: boolean;
|
||||||
@@ -17,6 +19,4 @@ export interface ProjectConfig {
|
|||||||
packageManager: PackageManager;
|
packageManager: PackageManager;
|
||||||
noInstall?: boolean;
|
noInstall?: boolean;
|
||||||
turso?: boolean;
|
turso?: boolean;
|
||||||
backendFramework: BackendFramework;
|
|
||||||
runtime: Runtime;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,32 +7,55 @@ export function displayConfig(config: Partial<ProjectConfig>) {
|
|||||||
if (config.projectName) {
|
if (config.projectName) {
|
||||||
configDisplay.push(`${pc.blue("Project Name:")} ${config.projectName}`);
|
configDisplay.push(`${pc.blue("Project Name:")} ${config.projectName}`);
|
||||||
}
|
}
|
||||||
if (config.database) {
|
|
||||||
|
if (config.backendFramework !== undefined) {
|
||||||
|
configDisplay.push(
|
||||||
|
`${pc.blue("Backend Framework:")} ${config.backendFramework}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.runtime !== undefined) {
|
||||||
|
configDisplay.push(`${pc.blue("Runtime:")} ${config.runtime}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.database !== undefined) {
|
||||||
configDisplay.push(`${pc.blue("Database:")} ${config.database}`);
|
configDisplay.push(`${pc.blue("Database:")} ${config.database}`);
|
||||||
}
|
}
|
||||||
if (config.orm) {
|
|
||||||
|
if (config.orm !== undefined) {
|
||||||
configDisplay.push(`${pc.blue("ORM:")} ${config.orm}`);
|
configDisplay.push(`${pc.blue("ORM:")} ${config.orm}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.auth !== undefined) {
|
if (config.auth !== undefined) {
|
||||||
configDisplay.push(`${pc.blue("Authentication:")} ${config.auth}`);
|
configDisplay.push(`${pc.blue("Authentication:")} ${config.auth}`);
|
||||||
}
|
}
|
||||||
if (config.runtime) {
|
|
||||||
configDisplay.push(`${pc.blue("Runtime:")} ${config.runtime}`);
|
if (config.addons !== undefined) {
|
||||||
|
const addonsText =
|
||||||
|
config.addons.length > 0 ? config.addons.join(", ") : "none";
|
||||||
|
configDisplay.push(`${pc.blue("Addons:")} ${addonsText}`);
|
||||||
}
|
}
|
||||||
if (config.addons?.length) {
|
|
||||||
configDisplay.push(`${pc.blue("Addons:")} ${config.addons.join(", ")}`);
|
if (config.examples !== undefined) {
|
||||||
|
const examplesText =
|
||||||
|
config.examples.length > 0 ? config.examples.join(", ") : "none";
|
||||||
|
configDisplay.push(`${pc.blue("Examples:")} ${examplesText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.git !== undefined) {
|
if (config.git !== undefined) {
|
||||||
configDisplay.push(`${pc.blue("Git Init:")} ${config.git}`);
|
configDisplay.push(`${pc.blue("Git Init:")} ${config.git}`);
|
||||||
}
|
}
|
||||||
if (config.packageManager) {
|
|
||||||
|
if (config.packageManager !== undefined) {
|
||||||
configDisplay.push(
|
configDisplay.push(
|
||||||
`${pc.blue("Package Manager:")} ${config.packageManager}`,
|
`${pc.blue("Package Manager:")} ${config.packageManager}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.noInstall !== undefined) {
|
if (config.noInstall !== undefined) {
|
||||||
configDisplay.push(`${pc.blue("Skip Install:")} ${config.noInstall}`);
|
configDisplay.push(`${pc.blue("Skip Install:")} ${config.noInstall}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.turso !== undefined) {
|
if (config.turso !== undefined) {
|
||||||
configDisplay.push(`${pc.blue("Turso Setup:")} ${config.turso}`);
|
configDisplay.push(`${pc.blue("Turso Setup:")} ${config.turso}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,42 +5,36 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
|
|
||||||
if (config.database === "none") {
|
if (config.database === "none") {
|
||||||
flags.push("--no-database");
|
flags.push("--no-database");
|
||||||
} else if (config.database === "sqlite") {
|
} else {
|
||||||
flags.push("--sqlite");
|
flags.push(`--${config.database}`);
|
||||||
} else if (config.database === "postgres") {
|
|
||||||
flags.push("--postgres");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.database !== "none") {
|
if (config.orm) {
|
||||||
if (config.orm === "drizzle") {
|
flags.push(`--${config.orm}`);
|
||||||
flags.push("--drizzle");
|
}
|
||||||
} else if (config.orm === "prisma") {
|
|
||||||
flags.push("--prisma");
|
if (config.database === "sqlite") {
|
||||||
|
flags.push(config.turso ? "--turso" : "--no-turso");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.auth) {
|
flags.push(config.auth ? "--auth" : "--no-auth");
|
||||||
flags.push("--auth");
|
|
||||||
} else {
|
|
||||||
flags.push("--no-auth");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.git) {
|
flags.push(config.git ? "--git" : "--no-git");
|
||||||
flags.push("--git");
|
|
||||||
} else {
|
|
||||||
flags.push("--no-git");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.noInstall) {
|
flags.push(config.noInstall ? "--no-install" : "--install");
|
||||||
flags.push("--no-install");
|
|
||||||
} else {
|
|
||||||
flags.push("--install");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.packageManager) {
|
if (config.packageManager) {
|
||||||
flags.push(`--${config.packageManager}`);
|
flags.push(`--${config.packageManager}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.backendFramework) {
|
||||||
|
flags.push(`--${config.backendFramework}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.runtime) {
|
||||||
|
flags.push(`--runtime ${config.runtime}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (config.addons.length > 0) {
|
if (config.addons.length > 0) {
|
||||||
for (const addon of config.addons) {
|
for (const addon of config.addons) {
|
||||||
flags.push(`--${addon}`);
|
flags.push(`--${addon}`);
|
||||||
@@ -55,21 +49,8 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
flags.push("--no-examples");
|
flags.push("--no-examples");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.database === "sqlite") {
|
|
||||||
if (config.turso) {
|
|
||||||
flags.push("--turso");
|
|
||||||
} else {
|
|
||||||
flags.push("--no-turso");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.runtime) {
|
|
||||||
flags.push(`--runtime ${config.runtime}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseCommand = "npx create-better-t-stack";
|
const baseCommand = "npx create-better-t-stack";
|
||||||
const projectName = config.projectName ? ` ${config.projectName}` : "";
|
const projectName = config.projectName ? ` ${config.projectName}` : "";
|
||||||
const flagString = flags.length > 0 ? ` ${flags.join(" ")}` : "";
|
|
||||||
|
|
||||||
return `${baseCommand}${projectName}${flagString}`;
|
return `${baseCommand}${projectName} ${flags.join(" ")}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,8 @@
|
|||||||
"compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server"
|
"compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hono/trpc-server": "^0.3.4",
|
|
||||||
"@trpc/server": "^11.0.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"hono": "^4.7.5",
|
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"types": ["node"],
|
"types": ["node", "bun"],
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"jsxImportSource": "hono/jsx"
|
"jsxImportSource": "hono/jsx"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { Context as HonoContext } from "hono";
|
import type { Context as ElysiaContext } from "elysia";
|
||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
|
|
||||||
export type CreateContextOptions = {
|
export type CreateContextOptions = {
|
||||||
hono: HonoContext;
|
context: ElysiaContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createContext({ hono }: CreateContextOptions) {
|
export async function createContext({ context }: CreateContextOptions) {
|
||||||
const session = await auth.api.getSession({
|
const session = await auth.api.getSession({
|
||||||
headers: hono.req.raw.headers,
|
headers: context.request.headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -2,12 +2,12 @@ import type { Context as HonoContext } from "hono";
|
|||||||
import { auth } from "./auth";
|
import { auth } from "./auth";
|
||||||
|
|
||||||
export type CreateContextOptions = {
|
export type CreateContextOptions = {
|
||||||
hono: HonoContext;
|
context: HonoContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createContext({ hono }: CreateContextOptions) {
|
export async function createContext({ context }: CreateContextOptions) {
|
||||||
const session = await auth.api.getSession({
|
const session = await auth.api.getSession({
|
||||||
headers: hono.req.raw.headers,
|
headers: context.req.raw.headers,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import * as schema from "../db/schema/auth";
|
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
|
import * as schema from "../db/schema/auth";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import "dotenv/config";
|
||||||
|
import { Elysia } from "elysia";
|
||||||
|
import { cors } from "@elysiajs/cors";
|
||||||
|
import { auth } from "./lib/auth";
|
||||||
|
import { createContext } from "./lib/context";
|
||||||
|
import { appRouter } from "./routers/index";
|
||||||
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||||
|
|
||||||
|
const app = new Elysia()
|
||||||
|
.use(
|
||||||
|
cors({
|
||||||
|
origin: process.env.CORS_ORIGIN || "",
|
||||||
|
methods: ["GET", "POST", "OPTIONS"],
|
||||||
|
allowedHeaders: ["Content-Type", "Authorization"],
|
||||||
|
credentials: true,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.all("/api/auth/*", async (context) => {
|
||||||
|
const { request } = context;
|
||||||
|
if (["POST", "GET"].includes(request.method)) {
|
||||||
|
return auth.handler(request);
|
||||||
|
} else {
|
||||||
|
context.error(405);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.all("/trpc/*", async (context) => {
|
||||||
|
const res = await fetchRequestHandler({
|
||||||
|
endpoint: "/trpc",
|
||||||
|
router: appRouter,
|
||||||
|
req: context.request,
|
||||||
|
createContext: () => createContext({ context }),
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.get("/", () => "OK")
|
||||||
|
.listen(3000, () => {
|
||||||
|
console.log(`Server is running on http://localhost:3000`);
|
||||||
|
});
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import { trpcServer } from "@hono/trpc-server";
|
import { trpcServer } from "@hono/trpc-server";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
@@ -27,8 +28,8 @@ app.use(
|
|||||||
"/trpc/*",
|
"/trpc/*",
|
||||||
trpcServer({
|
trpcServer({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: (_opts, hono) => {
|
createContext: (_opts, context) => {
|
||||||
return createContext({ hono });
|
return createContext({ context });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { router, publicProcedure } from "../lib/trpc";
|
|
||||||
import { todo } from "../db/schema";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { db } from "../db";
|
|
||||||
|
|
||||||
export const todoRouter = router({
|
|
||||||
getAll: publicProcedure.query(async () => {
|
|
||||||
return await db.select().from(todo).all();
|
|
||||||
}),
|
|
||||||
|
|
||||||
create: publicProcedure
|
|
||||||
.input(z.object({ text: z.string().min(1) }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await db
|
|
||||||
.insert(todo)
|
|
||||||
.values({
|
|
||||||
text: input.text,
|
|
||||||
})
|
|
||||||
.returning()
|
|
||||||
.get();
|
|
||||||
}),
|
|
||||||
|
|
||||||
toggle: publicProcedure
|
|
||||||
.input(z.object({ id: z.number(), completed: z.boolean() }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await db
|
|
||||||
.update(todo)
|
|
||||||
.set({ completed: input.completed })
|
|
||||||
.where(eq(todo.id, input.id))
|
|
||||||
.returning()
|
|
||||||
.get();
|
|
||||||
}),
|
|
||||||
|
|
||||||
delete: publicProcedure
|
|
||||||
.input(z.object({ id: z.number() }))
|
|
||||||
.mutation(async ({ input }) => {
|
|
||||||
return await db
|
|
||||||
.delete(todo)
|
|
||||||
.where(eq(todo.id, input.id))
|
|
||||||
.returning()
|
|
||||||
.get();
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { Context as HonoContext } from "hono";
|
|
||||||
import { auth } from "./auth";
|
|
||||||
|
|
||||||
export type CreateContextOptions = {
|
|
||||||
hono: HonoContext;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function createContext({ hono }: CreateContextOptions) {
|
|
||||||
const session = await auth.api.getSession({
|
|
||||||
headers: hono.req.raw.headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
session,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { initTRPC, TRPCError } from "@trpc/server";
|
|
||||||
import type { Context } from "./context";
|
|
||||||
|
|
||||||
export const t = initTRPC.context<Context>().create();
|
|
||||||
|
|
||||||
export const router = t.router;
|
|
||||||
|
|
||||||
export const publicProcedure = t.procedure;
|
|
||||||
|
|
||||||
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
|
||||||
if (!ctx.session) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Authentication required",
|
|
||||||
cause: "No session",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
...ctx,
|
|
||||||
session: ctx.session,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { initTRPC, TRPCError } from "@trpc/server";
|
|
||||||
import type { Context } from "./context";
|
|
||||||
|
|
||||||
export const t = initTRPC.context<Context>().create();
|
|
||||||
|
|
||||||
export const router = t.router;
|
|
||||||
|
|
||||||
export const publicProcedure = t.procedure;
|
|
||||||
|
|
||||||
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
|
||||||
if (!ctx.session) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Authentication required",
|
|
||||||
cause: "No session",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
...ctx,
|
|
||||||
session: ctx.session,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
27
apps/cli/template/with-elysia/apps/server/src/index.ts
Normal file
27
apps/cli/template/with-elysia/apps/server/src/index.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import "dotenv/config";
|
||||||
|
import { Elysia } from "elysia";
|
||||||
|
import { cors } from "@elysiajs/cors";
|
||||||
|
import { createContext } from "./lib/context";
|
||||||
|
import { appRouter } from "./routers/index";
|
||||||
|
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
||||||
|
|
||||||
|
const app = new Elysia()
|
||||||
|
.use(
|
||||||
|
cors({
|
||||||
|
origin: process.env.CORS_ORIGIN || "",
|
||||||
|
methods: ["GET", "POST", "OPTIONS"],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.all("/trpc/*", async (context) => {
|
||||||
|
const res = await fetchRequestHandler({
|
||||||
|
endpoint: "/trpc",
|
||||||
|
router: appRouter,
|
||||||
|
req: context.request,
|
||||||
|
createContext: () => createContext({ context }),
|
||||||
|
});
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.get("/", () => "OK")
|
||||||
|
.listen(3000, () => {
|
||||||
|
console.log(`Server is running on http://localhost:3000`);
|
||||||
|
});
|
||||||
13
apps/cli/template/with-elysia/apps/server/src/lib/context.ts
Normal file
13
apps/cli/template/with-elysia/apps/server/src/lib/context.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Context as ElysiaContext } from "elysia";
|
||||||
|
|
||||||
|
export type CreateContextOptions = {
|
||||||
|
context: ElysiaContext;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createContext({ context }: CreateContextOptions) {
|
||||||
|
return {
|
||||||
|
session: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
||||||
@@ -22,8 +22,8 @@ app.use(
|
|||||||
"/trpc/*",
|
"/trpc/*",
|
||||||
trpcServer({
|
trpcServer({
|
||||||
router: appRouter,
|
router: appRouter,
|
||||||
createContext: (_opts, hono) => {
|
createContext: (_opts, context) => {
|
||||||
return createContext({ hono });
|
return createContext({ context });
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Context as HonoContext } from "hono";
|
import type { Context as HonoContext } from "hono";
|
||||||
|
|
||||||
export type CreateContextOptions = {
|
export type CreateContextOptions = {
|
||||||
hono: HonoContext;
|
context: HonoContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createContext({ hono }: CreateContextOptions) {
|
export async function createContext({ context }: CreateContextOptions) {
|
||||||
return {
|
return {
|
||||||
session: null,
|
session: null,
|
||||||
};
|
};
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { Context as HonoContext } from "hono";
|
|
||||||
import { auth } from "./auth";
|
|
||||||
|
|
||||||
export type CreateContextOptions = {
|
|
||||||
hono: HonoContext;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function createContext({ hono }: CreateContextOptions) {
|
|
||||||
const session = await auth.api.getSession({
|
|
||||||
headers: hono.req.raw.headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
session,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Context = Awaited<ReturnType<typeof createContext>>;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { initTRPC, TRPCError } from "@trpc/server";
|
|
||||||
import type { Context } from "./context";
|
|
||||||
|
|
||||||
export const t = initTRPC.context<Context>().create();
|
|
||||||
|
|
||||||
export const router = t.router;
|
|
||||||
|
|
||||||
export const publicProcedure = t.procedure;
|
|
||||||
|
|
||||||
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
|
||||||
if (!ctx.session) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Authentication required",
|
|
||||||
cause: "No session",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
...ctx,
|
|
||||||
session: ctx.session,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { initTRPC, TRPCError } from "@trpc/server";
|
|
||||||
import type { Context } from "./context";
|
|
||||||
|
|
||||||
export const t = initTRPC.context<Context>().create();
|
|
||||||
|
|
||||||
export const router = t.router;
|
|
||||||
|
|
||||||
export const publicProcedure = t.procedure;
|
|
||||||
|
|
||||||
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
|
|
||||||
if (!ctx.session) {
|
|
||||||
throw new TRPCError({
|
|
||||||
code: "UNAUTHORIZED",
|
|
||||||
message: "Authentication required",
|
|
||||||
cause: "No session",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return next({
|
|
||||||
ctx: {
|
|
||||||
...ctx,
|
|
||||||
session: ctx.session,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user