mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
389 lines
10 KiB
TypeScript
389 lines
10 KiB
TypeScript
import path from "node:path";
|
|
import consola from "consola";
|
|
import fs from "fs-extra";
|
|
import type {
|
|
ProjectAddons,
|
|
ProjectConfig,
|
|
ProjectDatabase,
|
|
ProjectFrontend,
|
|
ProjectOrm,
|
|
ProjectRuntime,
|
|
} from "../types";
|
|
|
|
export async function createReadme(projectDir: string, options: ProjectConfig) {
|
|
const readmePath = path.join(projectDir, "README.md");
|
|
const content = generateReadmeContent(options);
|
|
|
|
try {
|
|
await fs.writeFile(readmePath, content);
|
|
} catch (error) {
|
|
consola.error("Failed to create README.md file:", error);
|
|
}
|
|
}
|
|
|
|
function generateReadmeContent(options: ProjectConfig): string {
|
|
const {
|
|
projectName,
|
|
packageManager,
|
|
database,
|
|
auth,
|
|
addons = [],
|
|
orm = "drizzle",
|
|
runtime = "bun",
|
|
frontend = ["tanstack-router"],
|
|
backend = "hono",
|
|
} = options;
|
|
|
|
const hasReactRouter = frontend.includes("react-router");
|
|
const hasTanstackRouter = frontend.includes("tanstack-router");
|
|
const hasNative = frontend.includes("native");
|
|
const hasNext = frontend.includes("next");
|
|
const hasTanstackStart = frontend.includes("tanstack-start");
|
|
const hasSvelte = frontend.includes("svelte");
|
|
const hasNuxt = frontend.includes("nuxt");
|
|
|
|
const packageManagerRunCmd =
|
|
packageManager === "npm" ? "npm run" : packageManager;
|
|
|
|
let webPort = "3001";
|
|
if (hasReactRouter || hasSvelte) {
|
|
webPort = "5173";
|
|
}
|
|
|
|
return `# ${projectName}
|
|
|
|
This project was created with [Better-T-Stack](https://github.com/AmanVarshney01/create-better-t-stack), a modern TypeScript stack that combines React, ${
|
|
hasTanstackRouter
|
|
? "TanStack Router"
|
|
: hasReactRouter
|
|
? "React Router"
|
|
: hasNext
|
|
? "Next.js"
|
|
: hasTanstackStart
|
|
? "TanStack Start"
|
|
: hasSvelte
|
|
? "SvelteKit"
|
|
: hasNuxt
|
|
? "Nuxt"
|
|
: ""
|
|
}, ${backend[0].toUpperCase() + backend.slice(1)}, tRPC, and more.
|
|
|
|
## Features
|
|
|
|
${generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend)}
|
|
|
|
## Getting Started
|
|
|
|
First, install the dependencies:
|
|
|
|
\`\`\`bash
|
|
${packageManager} install
|
|
\`\`\`
|
|
|
|
${generateDatabaseSetup(database, auth, packageManagerRunCmd, orm)}
|
|
|
|
Then, run the development server:
|
|
|
|
\`\`\`bash
|
|
${packageManagerRunCmd} dev
|
|
\`\`\`
|
|
|
|
${
|
|
hasTanstackRouter ||
|
|
hasReactRouter ||
|
|
hasNext ||
|
|
hasTanstackStart ||
|
|
hasSvelte ||
|
|
hasNuxt
|
|
? `Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see the web application.`
|
|
: ""
|
|
}
|
|
${hasNative ? "Use the Expo Go app to run the mobile application.\n" : ""}
|
|
The API is running at [http://localhost:3000](http://localhost:3000).
|
|
|
|
${
|
|
addons.includes("pwa") && hasReactRouter
|
|
? "\n## PWA Support with React Router v7\n\nThere is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809\n"
|
|
: ""
|
|
}
|
|
|
|
## Project Structure
|
|
|
|
\`\`\`
|
|
${projectName}/
|
|
├── apps/
|
|
${
|
|
hasTanstackRouter ||
|
|
hasReactRouter ||
|
|
hasNext ||
|
|
hasTanstackStart ||
|
|
hasSvelte ||
|
|
hasNuxt
|
|
? `│ ├── web/ # Frontend application (${
|
|
hasTanstackRouter
|
|
? "React + TanStack Router"
|
|
: hasReactRouter
|
|
? "React + React Router"
|
|
: hasNext
|
|
? "Next.js"
|
|
: hasTanstackStart
|
|
? "React + TanStack Start"
|
|
: hasSvelte
|
|
? "SvelteKit"
|
|
: hasNuxt
|
|
? "Nuxt"
|
|
: ""
|
|
})\n`
|
|
: ""
|
|
}${
|
|
hasNative
|
|
? "│ ├── native/ # Mobile application (React Native, Expo)\n"
|
|
: ""
|
|
}${
|
|
addons.includes("starlight")
|
|
? "│ ├── docs/ # Documentation site (Astro Starlight)\n"
|
|
: ""
|
|
}│ └── server/ # Backend API (${
|
|
backend[0].toUpperCase() + backend.slice(1)
|
|
}, tRPC)
|
|
\`\`\`
|
|
|
|
## Available Scripts
|
|
|
|
${generateScriptsList(
|
|
packageManagerRunCmd,
|
|
database,
|
|
orm,
|
|
auth,
|
|
hasNative,
|
|
addons,
|
|
backend,
|
|
)}
|
|
`;
|
|
}
|
|
|
|
function generateFeaturesList(
|
|
database: ProjectDatabase,
|
|
auth: boolean,
|
|
addons: ProjectAddons[],
|
|
orm: ProjectOrm,
|
|
runtime: ProjectRuntime,
|
|
frontend: ProjectFrontend[],
|
|
backend: string,
|
|
): string {
|
|
const hasTanstackRouter = frontend.includes("tanstack-router");
|
|
const hasReactRouter = frontend.includes("react-router");
|
|
const hasNative = frontend.includes("native");
|
|
const hasNext = frontend.includes("next");
|
|
const hasTanstackStart = frontend.includes("tanstack-start");
|
|
const hasSvelte = frontend.includes("svelte");
|
|
const hasNuxt = frontend.includes("nuxt");
|
|
|
|
const addonsList = [
|
|
"- **TypeScript** - For type safety and improved developer experience",
|
|
];
|
|
|
|
if (hasTanstackRouter) {
|
|
addonsList.push(
|
|
"- **TanStack Router** - File-based routing with full type safety",
|
|
);
|
|
} else if (hasReactRouter) {
|
|
addonsList.push("- **React Router** - Declarative routing for React");
|
|
} else if (hasNext) {
|
|
addonsList.push("- **Next.js** - Full-stack React framework");
|
|
} else if (hasTanstackStart) {
|
|
addonsList.push(
|
|
"- **TanStack Start** - SSR framework with TanStack Router",
|
|
);
|
|
} else if (hasSvelte) {
|
|
addonsList.push("- **SvelteKit** - Web framework for building Svelte apps");
|
|
} else if (hasNuxt) {
|
|
addonsList.push("- **Nuxt** - The Intuitive Vue Framework");
|
|
}
|
|
|
|
if (hasNative) {
|
|
addonsList.push("- **React Native** - Build mobile apps using React");
|
|
addonsList.push("- **Expo** - Tools for React Native development");
|
|
}
|
|
|
|
addonsList.push(
|
|
"- **TailwindCSS** - Utility-first CSS for rapid UI development",
|
|
"- **shadcn/ui** - Reusable UI components",
|
|
);
|
|
|
|
if (backend === "hono") {
|
|
addonsList.push("- **Hono** - Lightweight, performant server framework");
|
|
} else if (backend === "express") {
|
|
addonsList.push("- **Express** - Fast, unopinionated web framework");
|
|
} else if (backend === "elysia") {
|
|
addonsList.push("- **Elysia** - Type-safe, high-performance framework");
|
|
} else if (backend === "next") {
|
|
addonsList.push("- **Next.js** - Full-stack React framework");
|
|
}
|
|
|
|
addonsList.push(
|
|
"- **tRPC** - End-to-end type-safe APIs",
|
|
`- **${runtime === "bun" ? "Bun" : "Node.js"}** - Runtime environment`,
|
|
);
|
|
|
|
if (database !== "none") {
|
|
addonsList.push(
|
|
`- **${
|
|
orm === "drizzle" ? "Drizzle" : "Prisma"
|
|
}** - TypeScript-first ORM`,
|
|
`- **${
|
|
database === "sqlite"
|
|
? "SQLite/Turso"
|
|
: database === "postgres"
|
|
? "PostgreSQL"
|
|
: database === "mysql"
|
|
? "MySQL"
|
|
: "MongoDB"
|
|
}** - Database engine`,
|
|
);
|
|
}
|
|
|
|
if (auth) {
|
|
addonsList.push(
|
|
"- **Authentication** - Email & password authentication with Better Auth",
|
|
);
|
|
}
|
|
|
|
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");
|
|
} else if (addon === "starlight") {
|
|
addonsList.push("- **Starlight** - Documentation site with Astro");
|
|
}
|
|
}
|
|
|
|
return addonsList.join("\n");
|
|
}
|
|
|
|
function generateDatabaseSetup(
|
|
database: ProjectDatabase,
|
|
auth: boolean,
|
|
packageManagerRunCmd: string,
|
|
orm: ProjectOrm,
|
|
): string {
|
|
if (database === "none") {
|
|
return "";
|
|
}
|
|
|
|
let setup = "## Database Setup\n\n";
|
|
|
|
if (database === "sqlite") {
|
|
setup += `This project uses SQLite${
|
|
orm === "drizzle" ? " with Drizzle ORM" : " with Prisma"
|
|
}.
|
|
|
|
1. Start the local SQLite database:
|
|
\`\`\`bash
|
|
cd apps/server && ${packageManagerRunCmd} db:local
|
|
\`\`\`
|
|
|
|
2. Update your \`.env\` file in the \`apps/server\` directory with the appropriate connection details if needed.
|
|
`;
|
|
} else if (database === "postgres") {
|
|
setup += `This project uses PostgreSQL${
|
|
orm === "drizzle" ? " with Drizzle ORM" : " with Prisma"
|
|
}.
|
|
|
|
1. Make sure you have a PostgreSQL database set up.
|
|
2. Update your \`apps/server/.env\` file with your PostgreSQL connection details.
|
|
`;
|
|
} else if (database === "mysql") {
|
|
setup += `This project uses MySQL${
|
|
orm === "drizzle" ? " with Drizzle ORM" : " with Prisma"
|
|
}.
|
|
|
|
1. Make sure you have a MySQL database set up.
|
|
2. Update your \`apps/server/.env\` file with your MySQL connection details.
|
|
`;
|
|
} else if (database === "mongodb") {
|
|
setup += `This project uses MongoDB with Prisma ORM.
|
|
|
|
1. Make sure you have MongoDB set up.
|
|
2. Update your \`apps/server/.env\` file with your MongoDB connection URI.
|
|
`;
|
|
}
|
|
|
|
setup += `
|
|
${auth ? "3" : "3"}. ${
|
|
orm === "prisma"
|
|
? `Generate the Prisma client and push the schema:
|
|
\`\`\`bash
|
|
${packageManagerRunCmd} db:push
|
|
\`\`\``
|
|
: `Apply the schema to your database:
|
|
\`\`\`bash
|
|
${packageManagerRunCmd} db:push
|
|
\`\`\``
|
|
}
|
|
`;
|
|
|
|
return setup;
|
|
}
|
|
|
|
function generateScriptsList(
|
|
packageManagerRunCmd: string,
|
|
database: ProjectDatabase,
|
|
orm: ProjectOrm,
|
|
auth: boolean,
|
|
hasNative: boolean,
|
|
addons: ProjectAddons[],
|
|
backend: string,
|
|
): string {
|
|
let scripts = `- \`${packageManagerRunCmd} dev\`: Start all applications in development mode
|
|
- \`${packageManagerRunCmd} build\`: Build all applications
|
|
- \`${packageManagerRunCmd} dev:web\`: Start only the web application
|
|
- \`${packageManagerRunCmd} dev:server\`: Start only the server
|
|
- \`${packageManagerRunCmd} check-types\`: Check TypeScript types across all apps`;
|
|
|
|
if (hasNative) {
|
|
scripts += `
|
|
- \`${packageManagerRunCmd} dev:native\`: Start the React Native/Expo development server`;
|
|
}
|
|
|
|
if (database !== "none") {
|
|
scripts += `
|
|
- \`${packageManagerRunCmd} db:push\`: Push schema changes to database
|
|
- \`${packageManagerRunCmd} db:studio\`: Open database studio UI`;
|
|
|
|
if (database === "sqlite" && orm === "drizzle") {
|
|
scripts += `
|
|
- \`cd apps/server && ${packageManagerRunCmd} db:local\`: Start the local SQLite database`;
|
|
}
|
|
}
|
|
|
|
if (addons.includes("biome")) {
|
|
scripts += `
|
|
- \`${packageManagerRunCmd} check\`: Run Biome formatting and linting`;
|
|
}
|
|
|
|
if (addons.includes("pwa")) {
|
|
scripts += `
|
|
- \`cd apps/web && ${packageManagerRunCmd} generate-pwa-assets\`: Generate PWA assets`;
|
|
}
|
|
|
|
if (addons.includes("tauri")) {
|
|
scripts += `
|
|
- \`cd apps/web && ${packageManagerRunCmd} desktop:dev\`: Start Tauri desktop app in development
|
|
- \`cd apps/web && ${packageManagerRunCmd} desktop:build\`: Build Tauri desktop app`;
|
|
}
|
|
|
|
if (addons.includes("starlight")) {
|
|
scripts += `
|
|
- \`cd apps/docs && ${packageManagerRunCmd} dev\`: Start documentation site
|
|
- \`cd apps/docs && ${packageManagerRunCmd} build\`: Build documentation site`;
|
|
}
|
|
|
|
return scripts;
|
|
}
|