mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Add runtime selection feature between Bun and Node.js
This commit is contained in:
@@ -11,6 +11,7 @@ import { setupEnvironmentVariables } from "./env-setup";
|
||||
import { setupExamples } from "./examples-setup";
|
||||
import { displayPostInstallInstructions } from "./post-installation";
|
||||
import { initializeGit, updatePackageConfigurations } from "./project-config";
|
||||
import { setupRuntime } from "./runtime-setup";
|
||||
import {
|
||||
copyBaseTemplate,
|
||||
fixGitignoreFiles,
|
||||
@@ -38,6 +39,8 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
options.auth,
|
||||
);
|
||||
|
||||
await setupRuntime(projectDir, options.runtime);
|
||||
|
||||
await setupExamples(
|
||||
projectDir,
|
||||
options.examples,
|
||||
@@ -73,6 +76,7 @@ export async function createProject(options: ProjectConfig): Promise<string> {
|
||||
!options.noInstall,
|
||||
options.orm,
|
||||
options.addons,
|
||||
options.runtime,
|
||||
);
|
||||
|
||||
return projectDir;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { ProjectConfig, ProjectDatabase, ProjectOrm } from "../types";
|
||||
import type {
|
||||
ProjectAddons,
|
||||
ProjectConfig,
|
||||
ProjectDatabase,
|
||||
ProjectOrm,
|
||||
Runtime,
|
||||
} from "../types";
|
||||
|
||||
export async function createReadme(projectDir: string, options: ProjectConfig) {
|
||||
const readmePath = path.join(projectDir, "README.md");
|
||||
@@ -21,6 +27,7 @@ function generateReadmeContent(options: ProjectConfig): string {
|
||||
auth,
|
||||
addons = [],
|
||||
orm = "drizzle",
|
||||
runtime = "bun",
|
||||
} = options;
|
||||
|
||||
const packageManagerRunCmd =
|
||||
@@ -32,7 +39,7 @@ This project was created with [Better-T-Stack](https://github.com/better-t-stack
|
||||
|
||||
## Features
|
||||
|
||||
${generateFeaturesList(database, auth, addons, orm)}
|
||||
${generateFeaturesList(database, auth, addons, orm, runtime)}
|
||||
|
||||
## Getting Started
|
||||
|
||||
@@ -71,38 +78,46 @@ ${generateScriptsList(packageManagerRunCmd, database, orm, auth)}
|
||||
function generateFeaturesList(
|
||||
database: ProjectDatabase,
|
||||
auth: boolean,
|
||||
features: string[],
|
||||
addons: ProjectAddons[],
|
||||
orm: ProjectOrm,
|
||||
runtime: Runtime,
|
||||
): string {
|
||||
const featuresList = [
|
||||
const addonsList = [
|
||||
"- **TypeScript** - For type safety and improved developer experience",
|
||||
"- **TanStack Router** - File-based routing with full type safety",
|
||||
"- **TailwindCSS** - Utility-first CSS for rapid UI development",
|
||||
"- **shadcn/ui** - Reusable UI components",
|
||||
"- **Hono** - Lightweight, performant server framework",
|
||||
"- **tRPC** - End-to-end type-safe APIs",
|
||||
`- **${runtime === "bun" ? "Bun" : "Node.js"}** - Runtime environment`,
|
||||
];
|
||||
|
||||
if (database !== "none") {
|
||||
featuresList.push(
|
||||
addonsList.push(
|
||||
`- **${orm === "drizzle" ? "Drizzle" : "Prisma"}** - TypeScript-first ORM`,
|
||||
`- **${database === "sqlite" ? "SQLite/Turso" : "PostgreSQL"}** - Database engine`,
|
||||
);
|
||||
}
|
||||
|
||||
if (auth) {
|
||||
featuresList.push(
|
||||
addonsList.push(
|
||||
"- **Authentication** - Email & password authentication with Better Auth",
|
||||
);
|
||||
}
|
||||
|
||||
for (const feature of features) {
|
||||
if (feature === "docker") {
|
||||
featuresList.push("- **Docker** - Containerized deployment");
|
||||
for (const addon of addons) {
|
||||
if (addon === "pwa") {
|
||||
addonsList.push("- **PWA** - Progressive Web App support");
|
||||
} else if (addon === "tauri") {
|
||||
addonsList.push("- **Tauri** - Build native desktop applications");
|
||||
} else if (addon === "biome") {
|
||||
addonsList.push("- **Biome** - Linting and formatting");
|
||||
} else if (addon === "husky") {
|
||||
addonsList.push("- **Husky** - Git hooks for code quality");
|
||||
}
|
||||
}
|
||||
|
||||
return featuresList.join("\n");
|
||||
return addonsList.join("\n");
|
||||
}
|
||||
|
||||
function generateDatabaseSetup(
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
ProjectAddons,
|
||||
ProjectDatabase,
|
||||
ProjectOrm,
|
||||
Runtime,
|
||||
} from "../types";
|
||||
|
||||
export function displayPostInstallInstructions(
|
||||
@@ -14,6 +15,7 @@ export function displayPostInstallInstructions(
|
||||
depsInstalled: boolean,
|
||||
orm?: ProjectOrm,
|
||||
addons?: ProjectAddons[],
|
||||
runtime?: Runtime,
|
||||
) {
|
||||
const runCmd = packageManager === "npm" ? "npm run" : packageManager;
|
||||
const cdCmd = `cd ${projectName}`;
|
||||
@@ -21,7 +23,9 @@ export function displayPostInstallInstructions(
|
||||
addons?.includes("husky") || addons?.includes("biome");
|
||||
|
||||
const databaseInstructions =
|
||||
database !== "none" ? getDatabaseInstructions(database, orm, runCmd) : "";
|
||||
database !== "none"
|
||||
? getDatabaseInstructions(database, orm, runCmd, runtime)
|
||||
: "";
|
||||
const tauriInstructions = addons?.includes("tauri")
|
||||
? getTauriInstructions(runCmd)
|
||||
: "";
|
||||
@@ -49,6 +53,7 @@ function getDatabaseInstructions(
|
||||
database: ProjectDatabase,
|
||||
orm?: ProjectOrm,
|
||||
runCmd?: string,
|
||||
runtime?: Runtime,
|
||||
): string {
|
||||
const instructions = [];
|
||||
|
||||
@@ -59,6 +64,13 @@ function getDatabaseInstructions(
|
||||
`${pc.dim("Learn more at: https://www.prisma.io/docs/orm/overview/databases/turso")}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (runtime === "bun") {
|
||||
instructions.push(
|
||||
`${pc.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors, follow the guidance provided in the error messages`,
|
||||
);
|
||||
}
|
||||
|
||||
instructions.push(
|
||||
`${pc.cyan("•")} Apply schema: ${pc.dim(`${runCmd} db:push`)}`,
|
||||
);
|
||||
|
||||
94
apps/cli/src/helpers/runtime-setup.ts
Normal file
94
apps/cli/src/helpers/runtime-setup.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import path from "node:path";
|
||||
import fs from "fs-extra";
|
||||
import type { Runtime } from "../types";
|
||||
import { addPackageDependency } from "../utils/add-package-deps";
|
||||
|
||||
export async function setupRuntime(
|
||||
projectDir: string,
|
||||
runtime: Runtime,
|
||||
): Promise<void> {
|
||||
const serverDir = path.join(projectDir, "apps/server");
|
||||
const serverIndexPath = path.join(serverDir, "src/index.ts");
|
||||
|
||||
const indexContent = await fs.readFile(serverIndexPath, "utf-8");
|
||||
|
||||
if (runtime === "bun") {
|
||||
await setupBunRuntime(serverDir, serverIndexPath, indexContent);
|
||||
} else if (runtime === "node") {
|
||||
await setupNodeRuntime(serverDir, serverIndexPath, indexContent);
|
||||
}
|
||||
}
|
||||
|
||||
async function setupBunRuntime(
|
||||
serverDir: string,
|
||||
serverIndexPath: string,
|
||||
indexContent: string,
|
||||
): Promise<void> {
|
||||
const packageJsonPath = path.join(serverDir, "package.json");
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
dev: "bun run --hot src/index.ts",
|
||||
start: "bun run dist/src/index.js",
|
||||
};
|
||||
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
|
||||
addPackageDependency({
|
||||
devDependencies: ["@types/bun"],
|
||||
projectDir: serverDir,
|
||||
});
|
||||
|
||||
if (!indexContent.includes("export default app")) {
|
||||
const updatedContent = `${indexContent}\n\nexport default app;\n`;
|
||||
await fs.writeFile(serverIndexPath, updatedContent);
|
||||
}
|
||||
}
|
||||
|
||||
async function setupNodeRuntime(
|
||||
serverDir: string,
|
||||
serverIndexPath: string,
|
||||
indexContent: string,
|
||||
): 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);
|
||||
|
||||
packageJson.scripts = {
|
||||
...packageJson.scripts,
|
||||
dev: "tsx watch src/index.ts",
|
||||
start: "node dist/src/index.js",
|
||||
};
|
||||
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
|
||||
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}\`);
|
||||
},
|
||||
);\n`;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user