Skip to content

Commit 6db42a8

Browse files
committed
feat(web/cli/docs): add lefthook options to better-t-stack
1 parent 89d4a98 commit 6db42a8

37 files changed

+508
-102
lines changed

apps/cli/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ Options:
5858
--auth Include authentication
5959
--no-auth Exclude authentication
6060
--frontend <types...> Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-bare, native-uniwind, native-unistyles, none)
61-
--addons <types...> Additional addons (pwa, tauri, starlight, biome, husky, turborepo, fumadocs, ultracite, oxlint, none)
61+
--addons <types...> Additional addons (pwa, tauri, starlight, biome, turborepo, fumadocs, ultracite, oxlint, none)
62+
--hooks <type> Git hooks manager (lefthook, husky, none)
6263
--examples <types...> Examples to include (todo, ai, none)
6364
--git Initialize git repository
6465
--no-git Skip git initialization

apps/cli/src/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const DEFAULT_CONFIG_BASE = {
2424
api: "trpc",
2525
webDeploy: "none",
2626
serverDeploy: "none",
27+
hooks: "none",
2728
} as const;
2829

2930
export function getDefaultConfig() {
@@ -85,6 +86,7 @@ export const dependencyVersionMap = {
8586

8687
husky: "^9.1.7",
8788
"lint-staged": "^16.1.2",
89+
lefthook: "^2.0.8",
8890

8991
tsx: "^4.19.2",
9092
"@types/node": "^22.13.11",
@@ -175,7 +177,6 @@ export const ADDON_COMPATIBILITY = {
175177
pwa: ["tanstack-router", "react-router", "solid", "next"],
176178
tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid", "next"],
177179
biome: [],
178-
husky: [],
179180
turborepo: [],
180181
starlight: [],
181182
ultracite: [],

apps/cli/src/helpers/addons/addons-setup.ts

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,13 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
5858
}
5959
const hasUltracite = addons.includes("ultracite");
6060
const hasBiome = addons.includes("biome");
61-
const hasHusky = addons.includes("husky");
62-
const hasOxlint = addons.includes("oxlint");
6361

6462
if (hasUltracite) {
65-
await setupUltracite(config, hasHusky);
63+
await setupUltracite(config);
6664
} else {
6765
if (hasBiome) {
6866
await setupBiome(projectDir);
6967
}
70-
if (hasHusky) {
71-
let linter: "biome" | "oxlint" | undefined;
72-
if (hasOxlint) {
73-
linter = "oxlint";
74-
} else if (hasBiome) {
75-
linter = "biome";
76-
}
77-
await setupHusky(projectDir, linter);
78-
}
7968
}
8069

8170
if (addons.includes("oxlint")) {
@@ -123,39 +112,6 @@ export async function setupBiome(projectDir: string) {
123112
}
124113
}
125114

126-
export async function setupHusky(projectDir: string, linter?: "biome" | "oxlint") {
127-
await addPackageDependency({
128-
devDependencies: ["husky", "lint-staged"],
129-
projectDir,
130-
});
131-
132-
const packageJsonPath = path.join(projectDir, "package.json");
133-
if (await fs.pathExists(packageJsonPath)) {
134-
const packageJson = await fs.readJson(packageJsonPath);
135-
136-
packageJson.scripts = {
137-
...packageJson.scripts,
138-
prepare: "husky",
139-
};
140-
141-
if (linter === "oxlint") {
142-
packageJson["lint-staged"] = {
143-
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint",
144-
};
145-
} else if (linter === "biome") {
146-
packageJson["lint-staged"] = {
147-
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."],
148-
};
149-
} else {
150-
packageJson["lint-staged"] = {
151-
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "",
152-
};
153-
}
154-
155-
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
156-
}
157-
}
158-
159115
async function setupPwa(projectDir: string, frontends: Frontend[]) {
160116
const isCompatibleFrontend = frontends.some((f) =>
161117
["react-router", "tanstack-router", "solid"].includes(f),

apps/cli/src/helpers/addons/ultracite-setup.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ function getFrameworksFromFrontend(frontend: string[]): string[] {
130130
return Array.from(frameworks);
131131
}
132132

133-
export async function setupUltracite(config: ProjectConfig, hasHusky: boolean) {
134-
const { packageManager, projectDir, frontend } = config;
133+
export async function setupUltracite(config: ProjectConfig) {
134+
const { packageManager, projectDir, frontend, hooks: gitHooks } = config;
135135

136136
try {
137137
log.info("Setting up Ultracite...");
@@ -197,8 +197,10 @@ export async function setupUltracite(config: ProjectConfig, hasHusky: boolean) {
197197
ultraciteArgs.push("--hooks", ...hooks);
198198
}
199199

200-
if (hasHusky) {
200+
if (gitHooks === "husky") {
201201
ultraciteArgs.push("--integrations", "husky", "lint-staged");
202+
} else if (gitHooks === "lefthook") {
203+
ultraciteArgs.push("--integrations", "lefthook");
202204
}
203205

204206
const ultraciteArgsString = ultraciteArgs.join(" ");
@@ -215,11 +217,16 @@ export async function setupUltracite(config: ProjectConfig, hasHusky: boolean) {
215217
shell: true,
216218
});
217219

218-
if (hasHusky) {
220+
if (gitHooks === "husky") {
219221
await addPackageDependency({
220222
devDependencies: ["husky", "lint-staged"],
221223
projectDir,
222224
});
225+
} else if (gitHooks === "lefthook") {
226+
await addPackageDependency({
227+
devDependencies: ["lefthook"],
228+
projectDir,
229+
});
223230
}
224231

225232
s.stop("Ultracite setup successfully!");

apps/cli/src/helpers/core/add-addons.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export async function addAddonsToProject(
5050
api: detectedConfig.api || "none",
5151
webDeploy: detectedConfig.webDeploy || "none",
5252
serverDeploy: detectedConfig.serverDeploy || "none",
53+
hooks: input.hooks || detectedConfig.hooks || "none",
5354
};
5455

5556
for (const addon of input.addons) {

apps/cli/src/helpers/core/command-handlers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ export async function createProjectHandler(input: CreateInput & { projectName?:
102102
api: "none",
103103
webDeploy: "none",
104104
serverDeploy: "none",
105+
hooks: "none",
105106
} satisfies ProjectConfig,
106107
reproducibleCommand: "",
107108
timeScaffolded,

apps/cli/src/helpers/core/create-project.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { setupDatabase } from "../core/db-setup";
1212
import { setupRuntime } from "../core/runtime-setup";
1313
import { setupServerDeploy } from "../deployment/server-deploy-setup";
1414
import { setupWebDeploy } from "../deployment/web-deploy-setup";
15+
import { setupHooks } from "../hooks/hooks-setup";
1516
import { setupAuth } from "./auth-setup";
1617
import { createReadme } from "./create-readme";
1718
import { setupEnvironmentVariables } from "./env-setup";
@@ -30,6 +31,7 @@ import {
3031
setupDockerComposeTemplates,
3132
setupExamplesTemplate,
3233
setupFrontendTemplates,
34+
setupHooksTemplate,
3335
setupPaymentsTemplate,
3436
} from "./template-manager";
3537

@@ -59,6 +61,7 @@ export async function createProject(options: ProjectConfig, cliInput?: { manualD
5961
await setupExamplesTemplate(projectDir, options);
6062
}
6163
await setupAddonsTemplate(projectDir, options);
64+
await setupHooksTemplate(projectDir, options);
6265

6366
await setupDeploymentTemplates(projectDir, options);
6467

@@ -82,6 +85,10 @@ export async function createProject(options: ProjectConfig, cliInput?: { manualD
8285
await setupAddons(options);
8386
}
8487

88+
if (options.hooks && options.hooks !== "none") {
89+
await setupHooks(options);
90+
}
91+
8592
if (options.auth && options.auth !== "none") {
8693
await setupAuth(options);
8794
}

apps/cli/src/helpers/core/create-readme.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
Database,
99
DatabaseSetup,
1010
Frontend,
11+
Hooks,
1112
ORM,
1213
ProjectConfig,
1314
Runtime,
@@ -38,6 +39,7 @@ function generateReadmeContent(options: ProjectConfig) {
3839
api = "trpc",
3940
webDeploy,
4041
serverDeploy,
42+
hooks,
4143
} = options;
4244

4345
const isConvex = backend === "convex";
@@ -65,7 +67,7 @@ This project was created with [Better-T-Stack](https://github.com/AmanVarshney01
6567
6668
## Features
6769
68-
${generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api)}
70+
${generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api, hooks)}
6971
7072
## Getting Started
7173
@@ -348,6 +350,7 @@ function generateFeaturesList(
348350
frontend: Frontend[],
349351
backend: string,
350352
api: API,
353+
hooks?: Hooks,
351354
) {
352355
const isConvex = backend === "convex";
353356
const isBackendNone = backend === "none";
@@ -459,15 +462,19 @@ function generateFeaturesList(
459462
addonsList.push("- **Tauri** - Build native desktop applications");
460463
} else if (addon === "biome") {
461464
addonsList.push("- **Biome** - Linting and formatting");
462-
} else if (addon === "husky") {
463-
addonsList.push("- **Husky** - Git hooks for code quality");
464465
} else if (addon === "starlight") {
465466
addonsList.push("- **Starlight** - Documentation site with Astro");
466467
} else if (addon === "turborepo") {
467468
addonsList.push("- **Turborepo** - Optimized monorepo build system");
468469
}
469470
}
470471

472+
// Add hooks information
473+
if (hooks && hooks !== "none") {
474+
const hooksName = hooks === "husky" ? "Husky" : "Lefthook";
475+
addonsList.push(`- **${hooksName}** - Git hooks for code quality`);
476+
}
477+
471478
return addonsList.join("\n");
472479
}
473480

apps/cli/src/helpers/core/detect-project-config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export async function detectProjectConfig(projectDir: string) {
2323
api: btsConfig.api,
2424
webDeploy: btsConfig.webDeploy,
2525
serverDeploy: btsConfig.serverDeploy,
26+
hooks: btsConfig.hooks,
2627
};
2728
}
2829

apps/cli/src/helpers/core/post-installation.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ export async function displayPostInstallInstructions(
1919
dbSetup,
2020
webDeploy,
2121
serverDeploy,
22+
hooks,
2223
} = config;
2324

2425
const isConvex = backend === "convex";
2526
const isBackendSelf = backend === "self";
2627
const runCmd =
2728
packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : "bun run";
2829
const cdCmd = `cd ${relativePath}`;
29-
const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome");
30+
const hasHooksOrBiome = (hooks && hooks !== "none") || addons?.includes("biome");
3031

3132
const databaseInstructions =
3233
!isConvex && database !== "none"
@@ -42,7 +43,7 @@ export async function displayPostInstallInstructions(
4243
: "";
4344

4445
const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
45-
const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
46+
const lintingInstructions = hasHooksOrBiome ? getLintingInstructions(runCmd) : "";
4647
const nativeInstructions =
4748
(frontend?.includes("native-bare") ||
4849
frontend?.includes("native-uniwind") ||

0 commit comments

Comments
 (0)