diff --git a/README.md b/README.md index 8a490af6..549c1e4d 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ npx create-better-t-stack@latest - Databases: SQLite, PostgreSQL, MySQL, MongoDB (or none) - ORMs: Drizzle, Prisma, Mongoose (or none) - Auth: Better-Auth (optional) -- Addons: Turborepo, PWA, Tauri, Biome, Husky, Starlight, Fumadocs, Ruler, Ultracite, Oxlint +- Addons: Turborepo, PWA, Tauri, Biome, Lefthook, Husky, Starlight, Fumadocs, Ruler, Ultracite, Oxlint - Examples: Todo, AI - DB Setup: Turso, Neon, Supabase, Prisma PostgreSQL, MongoDB Atlas, Cloudflare D1, Docker - Web Deploy: Cloudflare Workers diff --git a/apps/cli/README.md b/apps/cli/README.md index 2a66686b..f78023b7 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -41,7 +41,7 @@ Follow the prompts to configure your project or use the `--yes` flag for default | **Database Setup** | • Turso (SQLite)
• Cloudflare D1 (SQLite)
• Neon (PostgreSQL)
• Supabase (PostgreSQL)
• Prisma Postgres
• MongoDB Atlas
• None (manual setup) | | **Authentication** | Better-Auth (email/password, with more options coming soon) | | **Styling** | Tailwind CSS with shadcn/ui components | -| **Addons** | • PWA support
• Tauri (desktop applications)
• Starlight (documentation site)
• Biome (linting and formatting)
• Husky (Git hooks)
• Turborepo (optimized builds) | +| **Addons** | • PWA support
• Tauri (desktop applications)
• Starlight (documentation site)
• Biome (linting and formatting)
• Lefthook, Husky (Git hooks)
• Turborepo (optimized builds) | | **Examples** | • Todo app
• AI Chat interface (using Vercel AI SDK) | | **Developer Experience** | • Automatic Git initialization
• Package manager choice (npm, pnpm, bun)
• Automatic dependency installation | @@ -58,7 +58,7 @@ Options: --auth Include authentication --no-auth Exclude authentication --frontend Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-bare, native-uniwind, native-unistyles, none) - --addons Additional addons (pwa, tauri, starlight, biome, husky, turborepo, fumadocs, ultracite, oxlint, none) + --addons Additional addons (pwa, tauri, starlight, biome, lefthook,husky, turborepo, fumadocs, ultracite, oxlint, none) --examples Examples to include (todo, ai, none) --git Initialize git repository --no-git Skip git initialization @@ -192,7 +192,7 @@ npx create-better-t-stack my-app --frontend none --backend hono --api trpc --dat - **ORM 'none'**: Can be used when you want to handle database operations manually or use a different ORM. - **Runtime 'none'**: Only available with Convex backend or when backend is 'none'. - **Cloudflare Workers runtime**: Only compatible with Hono backend, Drizzle ORM (or no ORM), and SQLite database (with D1 setup). Not compatible with MongoDB. -- **Addons 'none'**: Skips all addons (PWA, Tauri, Starlight, Biome, Husky, Turborepo). +- **Addons 'none'**: Skips all addons (PWA, Tauri, Starlight, Biome, Lefthook,Husky, Turborepo). - **Examples 'none'**: Skips all example implementations (todo, AI chat). - **SvelteKit, Nuxt, and SolidJS** frontends are only compatible with oRPC API layer - **PWA support** requires React with TanStack Router, React Router, or SolidJS diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index bf39476d..e945a506 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -85,6 +85,7 @@ export const dependencyVersionMap = { "@biomejs/biome": "^2.2.0", oxlint: "^1.32.0", + lefthook: "^2.0.11", husky: "^9.1.7", "lint-staged": "^16.1.2", @@ -178,6 +179,7 @@ export const ADDON_COMPATIBILITY = { tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid", "next"], biome: [], husky: [], + lefthook: [], turborepo: [], starlight: [], ultracite: [], diff --git a/apps/cli/src/helpers/addons/addons-setup.ts b/apps/cli/src/helpers/addons/addons-setup.ts index 26eb09fc..a3011bac 100644 --- a/apps/cli/src/helpers/addons/addons-setup.ts +++ b/apps/cli/src/helpers/addons/addons-setup.ts @@ -58,23 +58,31 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")} } const hasUltracite = addons.includes("ultracite"); const hasBiome = addons.includes("biome"); - const hasHusky = addons.includes("husky"); + const gitHook = addons.includes("husky") + ? "husky" + : addons.includes("lefthook") + ? "lefthook" + : ""; const hasOxlint = addons.includes("oxlint"); if (hasUltracite) { - await setupUltracite(config, hasHusky); + await setupUltracite(config, gitHook); } else { if (hasBiome) { await setupBiome(projectDir); } - if (hasHusky) { + if (gitHook) { let linter: "biome" | "oxlint" | undefined; if (hasOxlint) { linter = "oxlint"; } else if (hasBiome) { linter = "biome"; } - await setupHusky(projectDir, linter); + if (gitHook === "husky") { + await setupHusky(projectDir, linter); + } else if (gitHook === "lefthook") { + await setupLefthook(projectDir); + } } } @@ -156,6 +164,25 @@ export async function setupHusky(projectDir: string, linter?: "biome" | "oxlint" } } +export async function setupLefthook(projectDir: string) { + await addPackageDependency({ + devDependencies: ["lefthook"], + projectDir, + }); + + const packageJsonPath = path.join(projectDir, "package.json"); + if (await fs.pathExists(packageJsonPath)) { + const packageJson = await fs.readJson(packageJsonPath); + + packageJson.scripts = { + ...packageJson.scripts, + prepare: "lefthook install", + }; + + await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); + } +} + async function setupPwa(projectDir: string, frontends: Frontend[]) { const isCompatibleFrontend = frontends.some((f) => ["react-router", "tanstack-router", "solid"].includes(f), diff --git a/apps/cli/src/helpers/addons/ultracite-setup.ts b/apps/cli/src/helpers/addons/ultracite-setup.ts index 0da4d416..4775defa 100644 --- a/apps/cli/src/helpers/addons/ultracite-setup.ts +++ b/apps/cli/src/helpers/addons/ultracite-setup.ts @@ -2,7 +2,7 @@ import { autocompleteMultiselect, group, log, multiselect, spinner } from "@clac import { execa } from "execa"; import pc from "picocolors"; import type { ProjectConfig } from "../../types"; -import { addPackageDependency } from "../../utils/add-package-deps"; + import { exitCancelled } from "../../utils/errors"; import { getPackageExecutionCommand } from "../../utils/package-runner"; import { setupBiome } from "./addons-setup"; @@ -130,7 +130,7 @@ function getFrameworksFromFrontend(frontend: string[]): string[] { return Array.from(frameworks); } -export async function setupUltracite(config: ProjectConfig, hasHusky: boolean) { +export async function setupUltracite(config: ProjectConfig, gitHook: string) { const { packageManager, projectDir, frontend } = config; try { @@ -197,8 +197,11 @@ export async function setupUltracite(config: ProjectConfig, hasHusky: boolean) { ultraciteArgs.push("--hooks", ...hooks); } - if (hasHusky) { - ultraciteArgs.push("--integrations", "husky", "lint-staged"); + if (gitHook) { + ultraciteArgs.push("--integrations", `${gitHook}`); + if (gitHook === "husky") { + ultraciteArgs.push("lint-staged"); + } } const ultraciteArgsString = ultraciteArgs.join(" "); @@ -215,13 +218,6 @@ export async function setupUltracite(config: ProjectConfig, hasHusky: boolean) { shell: true, }); - if (hasHusky) { - await addPackageDependency({ - devDependencies: ["husky", "lint-staged"], - projectDir, - }); - } - s.stop("Ultracite setup successfully!"); } catch (error) { log.error(pc.red("Failed to set up Ultracite")); diff --git a/apps/cli/src/helpers/core/create-readme.ts b/apps/cli/src/helpers/core/create-readme.ts index 5fb53d69..73068a38 100644 --- a/apps/cli/src/helpers/core/create-readme.ts +++ b/apps/cli/src/helpers/core/create-readme.ts @@ -459,6 +459,8 @@ function generateFeaturesList( addonsList.push("- **Tauri** - Build native desktop applications"); } else if (addon === "biome") { addonsList.push("- **Biome** - Linting and formatting"); + } else if (addon === "lefthook") { + addonsList.push("- **Lefthook** - Fast and powerful Git hooks manager"); } else if (addon === "husky") { addonsList.push("- **Husky** - Git hooks for code quality"); } else if (addon === "starlight") { diff --git a/apps/cli/src/helpers/core/post-installation.ts b/apps/cli/src/helpers/core/post-installation.ts index 9bedc77c..1cadc607 100644 --- a/apps/cli/src/helpers/core/post-installation.ts +++ b/apps/cli/src/helpers/core/post-installation.ts @@ -26,7 +26,8 @@ export async function displayPostInstallInstructions( const runCmd = packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : "bun run"; const cdCmd = `cd ${relativePath}`; - const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome"); + const hasGitHooksOrBiome = + addons?.includes("husky") || addons?.includes("biome") || addons?.includes("lefthook"); const databaseInstructions = !isConvex && database !== "none" @@ -42,7 +43,7 @@ export async function displayPostInstallInstructions( : ""; const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : ""; - const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : ""; + const lintingInstructions = hasGitHooksOrBiome ? getLintingInstructions(runCmd) : ""; const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || diff --git a/apps/cli/src/prompts/addons.ts b/apps/cli/src/prompts/addons.ts index aa75b7d1..f35b7b8b 100644 --- a/apps/cli/src/prompts/addons.ts +++ b/apps/cli/src/prompts/addons.ts @@ -43,6 +43,10 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } { label = "Ruler"; hint = "Centralize your AI rules"; break; + case "lefthook": + label = "Lefthook"; + hint = "Fast and powerful Git hooks manager"; + break; case "husky": label = "Husky"; hint = "Modern native Git hooks made easy"; @@ -66,7 +70,7 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } { const ADDON_GROUPS = { Documentation: ["starlight", "fumadocs"], Linting: ["biome", "oxlint", "ultracite"], - Other: ["ruler", "turborepo", "pwa", "tauri", "husky"], + Other: ["ruler", "turborepo", "pwa", "tauri", "lefthook", "husky"], }; export async function getAddonsChoice(addons?: Addons[], frontends?: Frontend[], auth?: Auth) { diff --git a/apps/cli/templates/addons/lefthook/lefthook.yml.hbs b/apps/cli/templates/addons/lefthook/lefthook.yml.hbs new file mode 100644 index 00000000..1a2f654a --- /dev/null +++ b/apps/cli/templates/addons/lefthook/lefthook.yml.hbs @@ -0,0 +1,14 @@ +pre-commit: + commands: +{{#if (includes addons "biome")}} + biome-check: + run: biome check --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files} + stage_fixed: true +{{else if (includes addons "oxlint")}} + oxlint-fix: + run: oxlint --fix {staged_files} + stage_fixed: true +{{else}} + info: + run: echo "Go to https://lefthook.dev/ for more information on configuring hooks!" +{{/if}} \ No newline at end of file diff --git a/apps/cli/templates/addons/ruler/.ruler/bts.md.hbs b/apps/cli/templates/addons/ruler/.ruler/bts.md.hbs index 5764e3fe..d25d6e25 100644 --- a/apps/cli/templates/addons/ruler/.ruler/bts.md.hbs +++ b/apps/cli/templates/addons/ruler/.ruler/bts.md.hbs @@ -116,7 +116,7 @@ add Available addons you can add: - **Documentation**: Starlight, Fumadocs - **Linting**: Biome, Oxlint, Ultracite -- **Other**: Ruler, Turborepo, PWA, Tauri, Husky +- **Other**: Ruler, Turborepo, PWA, Tauri, Lefthook, Husky You can also add web deployment configurations like Cloudflare Workers support. diff --git a/apps/cli/test/addons.test.ts b/apps/cli/test/addons.test.ts index 043e845c..9fb21cd5 100644 --- a/apps/cli/test/addons.test.ts +++ b/apps/cli/test/addons.test.ts @@ -4,7 +4,7 @@ import { expectError, expectSuccess, runTRPCTest, type TestConfig } from "./test describe("Addon Configurations", () => { describe("Universal Addons (no frontend restrictions)", () => { - const universalAddons = ["biome", "husky", "turborepo", "oxlint"]; + const universalAddons = ["biome", "lefthook", "husky", "turborepo", "oxlint"]; for (const addon of universalAddons) { it(`should work with ${addon} addon on any frontend`, async () => { diff --git a/apps/web/content/docs/cli/options.mdx b/apps/web/content/docs/cli/options.mdx index 8cd5a8f1..38914c69 100644 --- a/apps/web/content/docs/cli/options.mdx +++ b/apps/web/content/docs/cli/options.mdx @@ -248,6 +248,7 @@ Additional features to include: - `starlight`: Starlight documentation site - `fumadocs`: Fumadocs documentation site - `biome`: Biome linting and formatting +- `lefthook`: Git hooks with Lefthook - `husky`: Git hooks with Husky - `turborepo`: Turborepo monorepo setup - `ultracite`: Ultracite configuration diff --git a/apps/web/content/docs/index.mdx b/apps/web/content/docs/index.mdx index b466ec21..58c09277 100644 --- a/apps/web/content/docs/index.mdx +++ b/apps/web/content/docs/index.mdx @@ -310,7 +310,7 @@ See the full list in the [CLI Reference](/docs/cli). Key flags: - `--orm`: drizzle, prisma, mongoose, none - `--api`: trpc, orpc, none - `--auth`: better-auth, clerk, none -- `--addons`: turborepo, pwa, tauri, biome, husky, starlight, fumadocs, ultracite, oxlint, ruler, none +- `--addons`: turborepo, pwa, tauri, biome, lefthook,husky, starlight, fumadocs, ultracite, oxlint, ruler, none - `--examples`: todo, ai, none ## Next Steps diff --git a/apps/web/content/docs/project-structure.mdx b/apps/web/content/docs/project-structure.mdx index 7db05de2..3659e306 100644 --- a/apps/web/content/docs/project-structure.mdx +++ b/apps/web/content/docs/project-structure.mdx @@ -254,7 +254,7 @@ apps/docs/ "backend": "", "runtime": "", "frontend": [""] , - "addons": [""] , + "addons": [""] , "examples": [""] , "auth": <"better-auth"|"clerk"|"none">, "packageManager": "", diff --git a/apps/web/src/lib/constant.ts b/apps/web/src/lib/constant.ts index e60da064..bca724e5 100644 --- a/apps/web/src/lib/constant.ts +++ b/apps/web/src/lib/constant.ts @@ -502,6 +502,14 @@ export const TECH_OPTIONS: Record< color: "from-green-500 to-green-700", default: false, }, + { + id: "lefthook", + name: "Lefthook", + description: "Fast and powerful Git hooks manager", + icon: `${ICON_BASE_URL}/lefthook.svg`, + color: "from-red-500 to-red-700", + default: false, + }, { id: "husky", name: "Husky", diff --git a/apps/web/src/lib/stack-utils.ts b/apps/web/src/lib/stack-utils.ts index 97c7dec1..1c30d16d 100644 --- a/apps/web/src/lib/stack-utils.ts +++ b/apps/web/src/lib/stack-utils.ts @@ -105,6 +105,7 @@ export function generateStackCommand(stack: StackState) { "tauri", "starlight", "biome", + "lefthook", "husky", "turborepo", "ultracite", diff --git a/packages/types/src/schemas.ts b/packages/types/src/schemas.ts index 6556da7b..36569da6 100644 --- a/packages/types/src/schemas.ts +++ b/packages/types/src/schemas.ts @@ -37,6 +37,7 @@ export const AddonsSchema = z "tauri", "starlight", "biome", + "lefthook", "husky", "ruler", "turborepo",