From 996b35b78aa2a1622e3b43672e5aaacb1fecd676 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Sat, 22 Mar 2025 22:51:59 +0530 Subject: [PATCH] Add Biome and Husky for code formatting and Git hooks --- .changeset/fluffy-dodos-act.md | 5 + .github/workflows/main.yaml | 14 ++ .../workflows/{release.yml => publish.yaml} | 24 +- apps/cli/src/constants.ts | 5 + apps/cli/src/helpers/addons-setup.ts | 60 +++++ apps/cli/src/helpers/install-dependencies.ts | 36 ++- apps/cli/src/helpers/post-installation.ts | 17 +- apps/cli/src/index.ts | 7 + apps/cli/src/prompts/addons.ts | 14 ++ apps/cli/src/types.ts | 2 +- apps/cli/template/base/package.json | 34 ++- .../template/base/packages/client/.prettierrc | 4 - .../base/packages/client/package.json | 96 ++++--- .../base/packages/client/src/main.tsx | 83 +++--- .../base/packages/client/src/routes/index.tsx | 6 +- .../base/packages/client/vite.config.ts | 2 +- .../base/packages/server/package.json | 46 ++-- .../base/packages/server/src/index.ts | 2 +- apps/cli/template/base/turbo.json | 50 ++-- .../client/src/components/sign-in-form.tsx | 238 +++++++++--------- .../with-auth/packages/client/src/main.tsx | 95 +++---- .../packages/client/src/routes/index.tsx | 132 +++++----- .../with-auth/packages/server/src/index.ts | 2 +- apps/cli/template/with-biome/biome.json | 42 ++++ .../packages/server/drizzle.config.ts | 2 +- .../packages/server/src/with-auth-lib/auth.ts | 2 +- .../packages/server/drizzle.config.ts | 2 +- .../packages/server/src/db/index.ts | 2 +- .../packages/server/src/with-auth-lib/auth.ts | 2 +- .../cli/template/with-husky/.husky/pre-commit | 1 + .../packages/server/src/with-auth-lib/auth.ts | 2 +- .../packages/server/src/with-auth-lib/auth.ts | 2 +- .../with-pwa/packages/client/vite.config.ts | 2 +- biome.json | 2 +- bun.lock | 2 +- package.json | 5 +- turbo.json | 4 +- 37 files changed, 609 insertions(+), 437 deletions(-) create mode 100644 .changeset/fluffy-dodos-act.md create mode 100644 .github/workflows/main.yaml rename .github/workflows/{release.yml => publish.yaml} (57%) delete mode 100644 apps/cli/template/base/packages/client/.prettierrc create mode 100644 apps/cli/template/with-biome/biome.json create mode 100644 apps/cli/template/with-husky/.husky/pre-commit diff --git a/.changeset/fluffy-dodos-act.md b/.changeset/fluffy-dodos-act.md new file mode 100644 index 0000000..7dedce1 --- /dev/null +++ b/.changeset/fluffy-dodos-act.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": minor +--- + +Add Biome and Husky for code formatting and Git hooks diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..93e0fe0 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,14 @@ +name: CI + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run check diff --git a/.github/workflows/release.yml b/.github/workflows/publish.yaml similarity index 57% rename from .github/workflows/release.yml rename to .github/workflows/publish.yaml index 14143a3..50d50c9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/publish.yaml @@ -1,32 +1,28 @@ -name: Release +name: Publish on: push: branches: - main paths: - - 'packages/cli/**' + - 'apps/cli/**' - '.changeset/**' - 'package.json' - 'bun.lock' +concurrency: ${{ github.workflow }}-${{ github.ref }} + jobs: - release: - name: Release + publish: runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 with: bun-version: latest - - - name: Install Dependencies - run: bun install - - - name: Create Release Pull Request or Publish + - run: bun install --frozen-lockfile + - name: Create Release Pull Request + id: changesets uses: changesets/action@v1 with: publish: bun run publish-packages diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index 39cf6eb..fc87ca8 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -33,6 +33,11 @@ export const dependencyVersionMap = { "@vite-pwa/assets-generator": "^0.2.6", "@tauri-apps/cli": "^2.4.0", + + "@biomejs/biome": "1.9.4", + + husky: "^9.1.7", + "lint-staged": "^15.5.0", } as const; export type AvailableDependencies = keyof typeof dependencyVersionMap; diff --git a/apps/cli/src/helpers/addons-setup.ts b/apps/cli/src/helpers/addons-setup.ts index 7c3e33a..86d499a 100644 --- a/apps/cli/src/helpers/addons-setup.ts +++ b/apps/cli/src/helpers/addons-setup.ts @@ -19,6 +19,66 @@ export async function setupAddons( if (addons.includes("tauri")) { await setupTauri(projectDir, packageManager); } + if (addons.includes("biome")) { + await setupBiome(projectDir); + } + if (addons.includes("husky")) { + await setupHusky(projectDir); + } +} + +async function setupBiome(projectDir: string) { + const biomeTemplateDir = path.join(PKG_ROOT, "template/with-biome"); + if (await fs.pathExists(biomeTemplateDir)) { + await fs.copy(biomeTemplateDir, projectDir, { overwrite: true }); + } + + addPackageDependency({ + devDependencies: ["@biomejs/biome"], + projectDir, + }); + + const packageJsonPath = path.join(projectDir, "package.json"); + if (await fs.pathExists(packageJsonPath)) { + const packageJson = await fs.readJson(packageJsonPath); + + packageJson.scripts = { + ...packageJson.scripts, + check: "biome check --write .", + }; + + await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); + } +} + +async function setupHusky(projectDir: string) { + const huskyTemplateDir = path.join(PKG_ROOT, "template/with-husky"); + if (await fs.pathExists(huskyTemplateDir)) { + await fs.copy(huskyTemplateDir, projectDir, { overwrite: true }); + } + + addPackageDependency({ + devDependencies: ["husky", "lint-staged"], + projectDir, + }); + + const packageJsonPath = path.join(projectDir, "package.json"); + if (await fs.pathExists(packageJsonPath)) { + const packageJson = await fs.readJson(packageJsonPath); + + packageJson.scripts = { + ...packageJson.scripts, + prepare: "husky", + }; + + packageJson["lint-staged"] = { + "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [ + "biome check --no-errors-on-unmatched --files-ignore-unknown=true", + ], + }; + + await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); + } } async function setupDocker(projectDir: string) { diff --git a/apps/cli/src/helpers/install-dependencies.ts b/apps/cli/src/helpers/install-dependencies.ts index 256f29d..3cabf4b 100644 --- a/apps/cli/src/helpers/install-dependencies.ts +++ b/apps/cli/src/helpers/install-dependencies.ts @@ -1,17 +1,18 @@ import { log, spinner } from "@clack/prompts"; import { $ } from "execa"; import pc from "picocolors"; +import type { ProjectAddons } from "../types"; import type { PackageManager } from "../utils/get-package-manager"; -interface InstallDependenciesOptions { - projectDir: string; - packageManager: PackageManager; -} - export async function installDependencies({ projectDir, packageManager, -}: InstallDependenciesOptions) { + addons = [], +}: { + projectDir: string; + packageManager: PackageManager; + addons?: ProjectAddons[]; +}) { const s = spinner(); try { @@ -34,6 +35,11 @@ export async function installDependencies({ } s.stop("Dependencies installed successfully"); + + // Run Biome check if Biome or Husky is enabled + if (addons.includes("biome") || addons.includes("husky")) { + await runBiomeCheck(projectDir, packageManager); + } } catch (error) { s.stop(pc.red("Failed to install dependencies")); if (error instanceof Error) { @@ -42,3 +48,21 @@ export async function installDependencies({ throw error; } } + +async function runBiomeCheck( + projectDir: string, + packageManager: PackageManager, +) { + const s = spinner(); + + try { + s.start("Running Biome format check..."); + + await $({ cwd: projectDir })`${packageManager} biome check --write .`; + + s.stop("Biome check completed successfully"); + } catch (error) { + s.stop(pc.yellow("Biome check encountered issues")); + log.warn(pc.yellow("Some files may need manual formatting")); + } +} diff --git a/apps/cli/src/helpers/post-installation.ts b/apps/cli/src/helpers/post-installation.ts index b17ad98..f5c8012 100644 --- a/apps/cli/src/helpers/post-installation.ts +++ b/apps/cli/src/helpers/post-installation.ts @@ -17,6 +17,17 @@ export function displayPostInstallInstructions( ) { const runCmd = packageManager === "npm" ? "npm run" : packageManager; const cdCmd = `cd ${projectName}`; + const hasHuskyOrBiome = + addons?.includes("husky") || addons?.includes("biome"); + + const databaseInstructions = + database !== "none" ? getDatabaseInstructions(database, orm, runCmd) : ""; + const tauriInstructions = addons?.includes("tauri") + ? getTauriInstructions(runCmd) + : ""; + const lintingInstructions = hasHuskyOrBiome + ? getLintingInstructions(runCmd) + : ""; log.info(`${pc.cyan("Project created successfully!")} @@ -27,9 +38,11 @@ ${!depsInstalled ? `${pc.cyan("2.")} ${packageManager} install\n` : ""}${pc.cyan ${pc.bold("Your project will be available at:")} ${pc.cyan("•")} Frontend: http://localhost:3001 ${pc.cyan("•")} API: http://localhost:3000 +${databaseInstructions ? `\n${databaseInstructions.trim()}` : ""}${tauriInstructions ? `\n${tauriInstructions.trim()}` : ""}${lintingInstructions ? `\n${lintingInstructions.trim()}` : ""}`); +} -${database !== "none" ? getDatabaseInstructions(database, orm, runCmd) : ""} -${addons?.includes("tauri") ? getTauriInstructions(runCmd) : ""}`); +function getLintingInstructions(runCmd?: string): string { + return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${pc.dim(`${runCmd} check`)}\n\n`; } function getDatabaseInstructions( diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index c5d5f47..7bbd35c 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -33,6 +33,8 @@ async function main() { .option("--docker", "Include Docker setup") .option("--pwa", "Include Progressive Web App support") .option("--tauri", "Include Tauri desktop app support") + .option("--biome", "Include Biome for linting and formatting") + .option("--husky", "Include Husky, lint-staged for Git hooks") .option("--no-addons", "Skip all additional addons") .option("--git", "Include git setup") .option("--no-git", "Skip git initialization") @@ -75,6 +77,8 @@ async function main() { ...((options.docker || options.pwa || options.tauri || + options.biome || + options.husky || options.addons === false) && { addons: options.addons === false @@ -83,6 +87,8 @@ async function main() { ...(options.docker ? ["docker"] : []), ...(options.pwa ? ["pwa"] : []), ...(options.tauri ? ["tauri"] : []), + ...(options.biome ? ["biome"] : []), + ...(options.husky ? ["husky"] : []), ] as ProjectAddons[]), }), }; @@ -141,6 +147,7 @@ async function main() { await installDependencies({ projectDir, packageManager: config.packageManager, + addons: config.addons, }); } diff --git a/apps/cli/src/prompts/addons.ts b/apps/cli/src/prompts/addons.ts index 5aba9ed..9e41377 100644 --- a/apps/cli/src/prompts/addons.ts +++ b/apps/cli/src/prompts/addons.ts @@ -25,6 +25,16 @@ export async function getAddonsChoice( label: "Tauri Desktop App", hint: "Build native desktop apps from your web frontend", }, + { + value: "biome", + label: "Biome", + hint: "Add Biome for linting and formatting", + }, + { + value: "husky", + label: "Husky", + hint: "Add Git hooks with Husky, lint-staged (requires Biome)", + }, ], required: false, }); @@ -34,5 +44,9 @@ export async function getAddonsChoice( process.exit(0); } + if (response.includes("husky") && !response.includes("biome")) { + response.push("biome"); + } + return response; } diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index b894453..ad934b3 100644 --- a/apps/cli/src/types.ts +++ b/apps/cli/src/types.ts @@ -1,7 +1,7 @@ export type ProjectDatabase = "sqlite" | "postgres" | "none"; export type ProjectOrm = "drizzle" | "prisma" | "none"; export type PackageManager = "npm" | "pnpm" | "yarn" | "bun"; -export type ProjectAddons = "docker" | "pwa" | "tauri"; +export type ProjectAddons = "docker" | "pwa" | "tauri" | "biome" | "husky"; export interface ProjectConfig { projectName: string; diff --git a/apps/cli/template/base/package.json b/apps/cli/template/base/package.json index 6810970..68a97f5 100644 --- a/apps/cli/template/base/package.json +++ b/apps/cli/template/base/package.json @@ -1,20 +1,18 @@ { - "name": "better-t-stack", - "private": true, - "workspaces": [ - "packages/*" - ], - "scripts": { - "dev": "turbo dev", - "build": "turbo build", - "check-types": "turbo check-types", - "dev:client": "turbo -F client dev", - "dev:server": "turbo -F server dev", - "db:push": "turbo -F server db:push", - "db:studio": "turbo -F server db:studio" - }, - "packageManager": "bun@1.2.4", - "devDependencies": { - "turbo": "^2.4.2" - } + "name": "better-t-stack", + "private": true, + "workspaces": ["packages/*"], + "scripts": { + "dev": "turbo dev", + "build": "turbo build", + "check-types": "turbo check-types", + "dev:client": "turbo -F client dev", + "dev:server": "turbo -F server dev", + "db:push": "turbo -F server db:push", + "db:studio": "turbo -F server db:studio" + }, + "packageManager": "bun@1.2.4", + "devDependencies": { + "turbo": "^2.4.2" + } } diff --git a/apps/cli/template/base/packages/client/.prettierrc b/apps/cli/template/base/packages/client/.prettierrc deleted file mode 100644 index 4189b56..0000000 --- a/apps/cli/template/base/packages/client/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "plugins": ["prettier-plugin-tailwindcss"], - "tailwindStylesheet": "./src/index.css" -} diff --git a/apps/cli/template/base/packages/client/package.json b/apps/cli/template/base/packages/client/package.json index 8314802..2ab9a0d 100644 --- a/apps/cli/template/base/packages/client/package.json +++ b/apps/cli/template/base/packages/client/package.json @@ -1,51 +1,49 @@ { - "name": "client", - "version": "0.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite --port=3001", - "build": "vite build", - "serve": "vite preview", - "start": "vite", - "check-types": "tsc --noEmit" - }, - "devDependencies": { - "@tanstack/router-plugin": "^1.114.27", - "@types/node": "^22.13.11", - "@types/react": "^19.0.12", - "@types/react-dom": "^19.0.4", - "@vitejs/plugin-react": "^4.3.4", - "postcss": "^8.5.3", - "prettier": "^3.5.3", - "prettier-plugin-tailwindcss": "^0.6.11", - "tailwindcss": "^4.0.15", - "vite": "^6.2.2" - }, - "dependencies": { - "@hookform/resolvers": "^3.10.0", - "@radix-ui/react-checkbox": "^1.1.4", - "@radix-ui/react-dropdown-menu": "^2.1.6", - "@radix-ui/react-label": "^2.1.2", - "@radix-ui/react-slot": "^1.1.2", - "@tanstack/react-form": "^1.0.5", - "@tailwindcss/vite": "^4.0.15", - "@tanstack/react-query": "^5.69.0", - "@tanstack/react-query-devtools": "^5.69.0", - "@tanstack/react-router": "^1.114.25", - "@tanstack/router-devtools": "^1.114.25", - "@trpc/client": "^11.0.0", - "@trpc/react-query": "^11.0.0", - "@trpc/server": "^11.0.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "lucide-react": "^0.473.0", - "next-themes": "^0.4.6", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "sonner": "^1.7.4", - "tailwind-merge": "^2.6.0", - "tailwindcss-animate": "^1.0.7", - "zod": "^3.24.2" - } + "name": "client", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --port=3001", + "build": "vite build", + "serve": "vite preview", + "start": "vite", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@tanstack/router-plugin": "^1.114.27", + "@types/node": "^22.13.11", + "@types/react": "^19.0.12", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "postcss": "^8.5.3", + "tailwindcss": "^4.0.15", + "vite": "^6.2.2" + }, + "dependencies": { + "@hookform/resolvers": "^3.10.0", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-slot": "^1.1.2", + "@tanstack/react-form": "^1.0.5", + "@tailwindcss/vite": "^4.0.15", + "@tanstack/react-query": "^5.69.0", + "@tanstack/react-query-devtools": "^5.69.0", + "@tanstack/react-router": "^1.114.25", + "@tanstack/router-devtools": "^1.114.25", + "@trpc/client": "^11.0.0", + "@trpc/react-query": "^11.0.0", + "@trpc/server": "^11.0.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.473.0", + "next-themes": "^0.4.6", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "sonner": "^1.7.4", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.24.2" + } } diff --git a/apps/cli/template/base/packages/client/src/main.tsx b/apps/cli/template/base/packages/client/src/main.tsx index 44783ba..71c12fd 100644 --- a/apps/cli/template/base/packages/client/src/main.tsx +++ b/apps/cli/template/base/packages/client/src/main.tsx @@ -1,7 +1,7 @@ import { - QueryCache, - QueryClient, - QueryClientProvider, + QueryCache, + QueryClient, + QueryClientProvider, } from "@tanstack/react-query"; import { RouterProvider, createRouter } from "@tanstack/react-router"; import { httpBatchLink } from "@trpc/client"; @@ -13,59 +13,60 @@ import { routeTree } from "./routeTree.gen"; import { trpc } from "./utils/trpc"; const queryClient = new QueryClient({ - queryCache: new QueryCache({ - onError: (error) => { - toast.error(error.message, { - action: { - label: "retry", - onClick: () => { - queryClient.invalidateQueries(); - }, - }, - }); - }, - }), + queryCache: new QueryCache({ + onError: (error) => { + toast.error(error.message, { + action: { + label: "retry", + onClick: () => { + queryClient.invalidateQueries(); + }, + }, + }); + }, + }), }); const trpcClient = trpc.createClient({ - links: [ - httpBatchLink({ - url: `${import.meta.env.VITE_SERVER_URL}/trpc`, - }), - ], + links: [ + httpBatchLink({ + url: `${import.meta.env.VITE_SERVER_URL}/trpc`, + }), + ], }); export const trpcQueryUtils = createTRPCQueryUtils({ - queryClient, - client: trpcClient, + queryClient, + client: trpcClient, }); const router = createRouter({ - routeTree, - defaultPreload: "intent", - context: { trpcQueryUtils }, - defaultPendingComponent: () => , - Wrap: function WrapComponent({ children }) { - return ( - - - {children} - - - ); - }, + routeTree, + defaultPreload: "intent", + context: { trpcQueryUtils }, + defaultPendingComponent: () => , + Wrap: function WrapComponent({ children }) { + return ( + + + {children} + + + ); + }, }); // Register things for typesafety declare module "@tanstack/react-router" { - interface Register { - router: typeof router; - } + interface Register { + router: typeof router; + } } -const rootElement = document.getElementById("app")!; +const rootElement = document.getElementById("app"); +if (!rootElement) throw new Error("Root element not found"); if (!rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement); - root.render(); + const root = ReactDOM.createRoot(rootElement); + root.render(); } diff --git a/apps/cli/template/base/packages/client/src/routes/index.tsx b/apps/cli/template/base/packages/client/src/routes/index.tsx index af1e5f6..e85e5d9 100644 --- a/apps/cli/template/base/packages/client/src/routes/index.tsx +++ b/apps/cli/template/base/packages/client/src/routes/index.tsx @@ -33,9 +33,9 @@ function HomeComponent() {

API Status

-
+
{healthCheck.isLoading ? "Checking..." diff --git a/apps/cli/template/base/packages/client/vite.config.ts b/apps/cli/template/base/packages/client/vite.config.ts index bfba367..8b87133 100644 --- a/apps/cli/template/base/packages/client/vite.config.ts +++ b/apps/cli/template/base/packages/client/vite.config.ts @@ -1,7 +1,7 @@ import tailwindcss from "@tailwindcss/vite"; import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; import react from "@vitejs/plugin-react"; -import path from "path"; +import path from "node:path"; import { defineConfig } from "vite"; export default defineConfig({ diff --git a/apps/cli/template/base/packages/server/package.json b/apps/cli/template/base/packages/server/package.json index 0a79eca..c46f850 100644 --- a/apps/cli/template/base/packages/server/package.json +++ b/apps/cli/template/base/packages/server/package.json @@ -1,25 +1,25 @@ { - "name": "server", - "main": "src/index.ts", - "type": "module", - "scripts": { - "dev": "tsx watch src/index.ts", - "build": "tsc", - "dev:bun": "bun run --hot src/index.ts", - "check-types": "tsc --noEmit", - "compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server" - }, - "dependencies": { - "@hono/node-server": "^1.13.8", - "@hono/trpc-server": "^0.3.4", - "@trpc/server": "^11.0.0", - "dotenv": "^16.4.7", - "hono": "^4.7.5", - "zod": "^3.24.2" - }, - "devDependencies": { - "tsx": "^4.19.2", - "@types/node": "^22.13.11", - "typescript": "^5.8.2" - } + "name": "server", + "main": "src/index.ts", + "type": "module", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "dev:bun": "bun run --hot src/index.ts", + "check-types": "tsc --noEmit", + "compile": "bun build --compile --minify --sourcemap --bytecode ./src/index.ts --outfile server" + }, + "dependencies": { + "@hono/node-server": "^1.13.8", + "@hono/trpc-server": "^0.3.4", + "@trpc/server": "^11.0.0", + "dotenv": "^16.4.7", + "hono": "^4.7.5", + "zod": "^3.24.2" + }, + "devDependencies": { + "tsx": "^4.19.2", + "@types/node": "^22.13.11", + "typescript": "^5.8.2" + } } diff --git a/apps/cli/template/base/packages/server/src/index.ts b/apps/cli/template/base/packages/server/src/index.ts index 9ea53d1..2ce846e 100644 --- a/apps/cli/template/base/packages/server/src/index.ts +++ b/apps/cli/template/base/packages/server/src/index.ts @@ -14,7 +14,7 @@ app.use(logger()); app.use( "/*", cors({ - origin: process.env.CORS_ORIGIN!, + origin: process.env.CORS_ORIGIN || "", allowMethods: ["GET", "POST", "OPTIONS"], }), ); diff --git a/apps/cli/template/base/turbo.json b/apps/cli/template/base/turbo.json index 36ac4c7..487d7dc 100644 --- a/apps/cli/template/base/turbo.json +++ b/apps/cli/template/base/turbo.json @@ -1,27 +1,27 @@ { - "$schema": "https://turbo.build/schema.json", - "ui": "tui", - "tasks": { - "build": { - "dependsOn": ["^build"], - "inputs": ["$TURBO_DEFAULT$", ".env*"], - "outputs": ["dist/**"] - }, - "lint": { - "dependsOn": ["^lint"] - }, - "check-types": { - "dependsOn": ["^check-types"] - }, - "dev": { - "cache": false, - "persistent": true - }, - "db:push": { - "cache": false - }, - "db:studio": { - "cache": false - } - } + "$schema": "https://turbo.build/schema.json", + "ui": "tui", + "tasks": { + "build": { + "dependsOn": ["^build"], + "inputs": ["$TURBO_DEFAULT$", ".env*"], + "outputs": ["dist/**"] + }, + "lint": { + "dependsOn": ["^lint"] + }, + "check": { + "dependsOn": ["^check-types"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "db:push": { + "cache": false + }, + "db:studio": { + "cache": false + } + } } diff --git a/apps/cli/template/with-auth/packages/client/src/components/sign-in-form.tsx b/apps/cli/template/with-auth/packages/client/src/components/sign-in-form.tsx index 6bae864..7137c4a 100644 --- a/apps/cli/template/with-auth/packages/client/src/components/sign-in-form.tsx +++ b/apps/cli/template/with-auth/packages/client/src/components/sign-in-form.tsx @@ -9,133 +9,131 @@ import { Input } from "./ui/input"; import { Label } from "./ui/label"; export default function SignInForm({ - onSwitchToSignUp, + onSwitchToSignUp, }: { - onSwitchToSignUp: () => void; + onSwitchToSignUp: () => void; }) { - const navigate = useNavigate({ - from: "/", - }); - const { isPending } = authClient.useSession(); + const navigate = useNavigate({ + from: "/", + }); + const { isPending } = authClient.useSession(); - const form = useForm({ - defaultValues: { - email: "", - password: "", - }, - onSubmit: async ({ value }) => { - await authClient.signIn.email( - { - email: value.email, - password: value.password, - }, - { - onSuccess: () => { - toast.success("Sign in successful"); - navigate({ - to: "/dashboard", - }); - }, - onError: (error) => { - toast.error(error.error.message); - }, - }, - ); - }, - validators: { - onSubmit: z.object({ - email: z.string().email("Invalid email address"), - password: z.string().min(6, "Password must be at least 6 characters"), - }), - }, - }); + const form = useForm({ + defaultValues: { + email: "", + password: "", + }, + onSubmit: async ({ value }) => { + await authClient.signIn.email( + { + email: value.email, + password: value.password, + }, + { + onSuccess: () => { + toast.success("Sign in successful"); + navigate({ + to: "/dashboard", + }); + }, + onError: (error) => { + toast.error(error.error.message); + }, + }, + ); + }, + validators: { + onSubmit: z.object({ + email: z.string().email("Invalid email address"), + password: z.string().min(6, "Password must be at least 6 characters"), + }), + }, + }); - if (isPending) { - return ; - } + if (isPending) { + return ; + } - return ( -
-

Welcome Back

+ return ( +
+

Welcome Back

-
{ - e.preventDefault(); - e.stopPropagation(); - void form.handleSubmit(); - }} - className="space-y-4" - > -
- ( -
- - field.handleChange(e.target.value)} - /> - {field.state.meta.errors.map((error) => ( -

- {error?.message} -

- ))} -
- )} - /> -
+ { + e.preventDefault(); + e.stopPropagation(); + void form.handleSubmit(); + }} + className="space-y-4" + > +
+ + {(field) => ( +
+ + field.handleChange(e.target.value)} + /> + {field.state.meta.errors.map((error) => ( +

+ {error?.message} +

+ ))} +
+ )} +
+
-
- ( -
- - field.handleChange(e.target.value)} - /> - {field.state.meta.errors.map((error) => ( -

- {error?.message} -

- ))} -
- )} - /> -
+
+ + {(field) => ( +
+ + field.handleChange(e.target.value)} + /> + {field.state.meta.errors.map((error) => ( +

+ {error?.message} +

+ ))} +
+ )} +
+
- - {(state) => ( - - )} - -
+ + {(state) => ( + + )} + + -
- -
-
- ); +
+ +
+
+ ); } diff --git a/apps/cli/template/with-auth/packages/client/src/main.tsx b/apps/cli/template/with-auth/packages/client/src/main.tsx index 904f39a..8bbe645 100644 --- a/apps/cli/template/with-auth/packages/client/src/main.tsx +++ b/apps/cli/template/with-auth/packages/client/src/main.tsx @@ -1,7 +1,7 @@ import { - QueryCache, - QueryClient, - QueryClientProvider, + QueryCache, + QueryClient, + QueryClientProvider, } from "@tanstack/react-query"; import { RouterProvider, createRouter } from "@tanstack/react-router"; import { httpBatchLink } from "@trpc/client"; @@ -13,65 +13,66 @@ import { routeTree } from "./routeTree.gen"; import { trpc } from "./utils/trpc"; const queryClient = new QueryClient({ - queryCache: new QueryCache({ - onError: (error) => { - toast.error(error.message, { - action: { - label: "retry", - onClick: () => { - queryClient.invalidateQueries(); - }, - }, - }); - }, - }), + queryCache: new QueryCache({ + onError: (error) => { + toast.error(error.message, { + action: { + label: "retry", + onClick: () => { + queryClient.invalidateQueries(); + }, + }, + }); + }, + }), }); const trpcClient = trpc.createClient({ - links: [ - httpBatchLink({ - url: `${import.meta.env.VITE_SERVER_URL}/trpc`, - fetch(url, options) { - return fetch(url, { - ...options, - credentials: "include", - }); - }, - }), - ], + links: [ + httpBatchLink({ + url: `${import.meta.env.VITE_SERVER_URL}/trpc`, + fetch(url, options) { + return fetch(url, { + ...options, + credentials: "include", + }); + }, + }), + ], }); export const trpcQueryUtils = createTRPCQueryUtils({ - queryClient, - client: trpcClient, + queryClient, + client: trpcClient, }); const router = createRouter({ - routeTree, - defaultPreload: "intent", - context: { trpcQueryUtils }, - defaultPendingComponent: () => , - Wrap: function WrapComponent({ children }) { - return ( - - - {children} - - - ); - }, + routeTree, + defaultPreload: "intent", + context: { trpcQueryUtils }, + defaultPendingComponent: () => , + Wrap: function WrapComponent({ children }) { + return ( + + + {children} + + + ); + }, }); // Register things for typesafety declare module "@tanstack/react-router" { - interface Register { - router: typeof router; - } + interface Register { + router: typeof router; + } } -const rootElement = document.getElementById("app")!; +const rootElement = document.getElementById("app"); +if (!rootElement) throw new Error("Root element not found"); if (!rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement); - root.render(); + const root = ReactDOM.createRoot(rootElement); + root.render(); } diff --git a/apps/cli/template/with-auth/packages/client/src/routes/index.tsx b/apps/cli/template/with-auth/packages/client/src/routes/index.tsx index af1e5f6..34358de 100644 --- a/apps/cli/template/with-auth/packages/client/src/routes/index.tsx +++ b/apps/cli/template/with-auth/packages/client/src/routes/index.tsx @@ -1,10 +1,10 @@ -import { trpc } from "@/utils/trpc"; -import { createFileRoute, Link } from "@tanstack/react-router"; -import { ArrowRight } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { trpc } from "@/utils/trpc"; +import { Link, createFileRoute } from "@tanstack/react-router"; +import { ArrowRight } from "lucide-react"; export const Route = createFileRoute("/")({ - component: HomeComponent, + component: HomeComponent, }); const TITLE_TEXT = ` @@ -24,74 +24,74 @@ const TITLE_TEXT = ` `; function HomeComponent() { - const healthCheck = trpc.healthCheck.useQuery(); + const healthCheck = trpc.healthCheck.useQuery(); - return ( -
-
{TITLE_TEXT}
-
-
-

API Status

-
-
- - {healthCheck.isLoading - ? "Checking..." - : healthCheck.data - ? "Connected" - : "Disconnected"} - -
-
+ return ( +
+
{TITLE_TEXT}
+
+
+

API Status

+
+
+ + {healthCheck.isLoading + ? "Checking..." + : healthCheck.data + ? "Connected" + : "Disconnected"} + +
+
-
-

Core Features

-
    - - - - -
-
+
+

Core Features

+
    + + + + +
+
-
- -
-
-
- ); +
+ +
+
+
+ ); } function FeatureItem({ - title, - description, + title, + description, }: { - title: string; - description: string; + title: string; + description: string; }) { - return ( -
  • -

    {title}

    -

    {description}

    -
  • - ); + return ( +
  • +

    {title}

    +

    {description}

    +
  • + ); } diff --git a/apps/cli/template/with-auth/packages/server/src/index.ts b/apps/cli/template/with-auth/packages/server/src/index.ts index c0715ae..4496748 100644 --- a/apps/cli/template/with-auth/packages/server/src/index.ts +++ b/apps/cli/template/with-auth/packages/server/src/index.ts @@ -15,7 +15,7 @@ app.use(logger()); app.use( "/*", cors({ - origin: process.env.CORS_ORIGIN!, + origin: process.env.CORS_ORIGIN || "", allowMethods: ["GET", "POST", "OPTIONS"], allowHeaders: ["Content-Type", "Authorization"], credentials: true, diff --git a/apps/cli/template/with-biome/biome.json b/apps/cli/template/with-biome/biome.json new file mode 100644 index 0000000..14360d8 --- /dev/null +++ b/apps/cli/template/with-biome/biome.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [ + ".next", + "dist", + ".turbo", + "dev-dist", + ".zed", + ".vscode", + "routeTree.gen.ts", + "src-tauri" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "useExhaustiveDependencies": "info" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/apps/cli/template/with-drizzle-postgres/packages/server/drizzle.config.ts b/apps/cli/template/with-drizzle-postgres/packages/server/drizzle.config.ts index 50fb7f6..78168de 100644 --- a/apps/cli/template/with-drizzle-postgres/packages/server/drizzle.config.ts +++ b/apps/cli/template/with-drizzle-postgres/packages/server/drizzle.config.ts @@ -5,6 +5,6 @@ export default defineConfig({ out: "./migrations", dialect: "postgresql", dbCredentials: { - url: process.env.POSTGRES_URL!, + url: process.env.POSTGRES_URL || "", }, }); diff --git a/apps/cli/template/with-drizzle-postgres/packages/server/src/with-auth-lib/auth.ts b/apps/cli/template/with-drizzle-postgres/packages/server/src/with-auth-lib/auth.ts index d3e8669..3ef3946 100644 --- a/apps/cli/template/with-drizzle-postgres/packages/server/src/with-auth-lib/auth.ts +++ b/apps/cli/template/with-drizzle-postgres/packages/server/src/with-auth-lib/auth.ts @@ -8,7 +8,7 @@ export const auth = betterAuth({ provider: "pg", schema: schema, }), - trustedOrigins: [process.env.CORS_ORIGIN!], + trustedOrigins: [process.env.CORS_ORIGIN || ""], emailAndPassword: { enabled: true, }, diff --git a/apps/cli/template/with-drizzle-sqlite/packages/server/drizzle.config.ts b/apps/cli/template/with-drizzle-sqlite/packages/server/drizzle.config.ts index 908a9d4..fb4ba63 100644 --- a/apps/cli/template/with-drizzle-sqlite/packages/server/drizzle.config.ts +++ b/apps/cli/template/with-drizzle-sqlite/packages/server/drizzle.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ out: "./migrations", dialect: "turso", dbCredentials: { - url: process.env.TURSO_CONNECTION_URL!, + url: process.env.TURSO_CONNECTION_URL || "", authToken: process.env.TURSO_AUTH_TOKEN, }, }); diff --git a/apps/cli/template/with-drizzle-sqlite/packages/server/src/db/index.ts b/apps/cli/template/with-drizzle-sqlite/packages/server/src/db/index.ts index c06055c..f06fd5c 100644 --- a/apps/cli/template/with-drizzle-sqlite/packages/server/src/db/index.ts +++ b/apps/cli/template/with-drizzle-sqlite/packages/server/src/db/index.ts @@ -2,7 +2,7 @@ import { drizzle } from "drizzle-orm/libsql"; export const db = drizzle({ connection: { - url: process.env.TURSO_CONNECTION_URL!, + url: process.env.TURSO_CONNECTION_URL || "", authToken: process.env.TURSO_AUTH_TOKEN, }, // logger: true, diff --git a/apps/cli/template/with-drizzle-sqlite/packages/server/src/with-auth-lib/auth.ts b/apps/cli/template/with-drizzle-sqlite/packages/server/src/with-auth-lib/auth.ts index 4531d67..49511e5 100644 --- a/apps/cli/template/with-drizzle-sqlite/packages/server/src/with-auth-lib/auth.ts +++ b/apps/cli/template/with-drizzle-sqlite/packages/server/src/with-auth-lib/auth.ts @@ -8,7 +8,7 @@ export const auth = betterAuth({ provider: "sqlite", schema: schema, }), - trustedOrigins: [process.env.CORS_ORIGIN!], + trustedOrigins: [process.env.CORS_ORIGIN || ""], emailAndPassword: { enabled: true, }, diff --git a/apps/cli/template/with-husky/.husky/pre-commit b/apps/cli/template/with-husky/.husky/pre-commit new file mode 100644 index 0000000..c27d889 --- /dev/null +++ b/apps/cli/template/with-husky/.husky/pre-commit @@ -0,0 +1 @@ +lint-staged diff --git a/apps/cli/template/with-prisma-postgres/packages/server/src/with-auth-lib/auth.ts b/apps/cli/template/with-prisma-postgres/packages/server/src/with-auth-lib/auth.ts index 1e95451..475ad86 100644 --- a/apps/cli/template/with-prisma-postgres/packages/server/src/with-auth-lib/auth.ts +++ b/apps/cli/template/with-prisma-postgres/packages/server/src/with-auth-lib/auth.ts @@ -6,7 +6,7 @@ export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "pg", }), - trustedOrigins: [process.env.CORS_ORIGIN!], + trustedOrigins: [process.env.CORS_ORIGIN || ""], emailAndPassword: { enabled: true }, advanced: { defaultCookieAttributes: { diff --git a/apps/cli/template/with-prisma-sqlite/packages/server/src/with-auth-lib/auth.ts b/apps/cli/template/with-prisma-sqlite/packages/server/src/with-auth-lib/auth.ts index 3007cb2..e549c72 100644 --- a/apps/cli/template/with-prisma-sqlite/packages/server/src/with-auth-lib/auth.ts +++ b/apps/cli/template/with-prisma-sqlite/packages/server/src/with-auth-lib/auth.ts @@ -6,7 +6,7 @@ export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "sqlite", }), - trustedOrigins: [process.env.CORS_ORIGIN!], + trustedOrigins: [process.env.CORS_ORIGIN || ""], emailAndPassword: { enabled: true }, advanced: { defaultCookieAttributes: { diff --git a/apps/cli/template/with-pwa/packages/client/vite.config.ts b/apps/cli/template/with-pwa/packages/client/vite.config.ts index 8a3af77..b196ecf 100644 --- a/apps/cli/template/with-pwa/packages/client/vite.config.ts +++ b/apps/cli/template/with-pwa/packages/client/vite.config.ts @@ -1,7 +1,7 @@ import tailwindcss from "@tailwindcss/vite"; import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; import react from "@vitejs/plugin-react"; -import path from "path"; +import path from "node:path"; import { defineConfig } from "vite"; import { VitePWA } from "vite-plugin-pwa"; diff --git a/biome.json b/biome.json index 7410ce9..239bcd8 100644 --- a/biome.json +++ b/biome.json @@ -7,7 +7,7 @@ }, "files": { "ignoreUnknown": false, - "ignore": [".next", "dist", ".source", "out", "template"] + "ignore": [".next", "dist", ".source", "out", "template", ".turbo"] }, "formatter": { "enabled": true, diff --git a/bun.lock b/bun.lock index 0370b2a..c6e0404 100644 --- a/bun.lock +++ b/bun.lock @@ -14,7 +14,7 @@ }, "apps/cli": { "name": "create-better-t-stack", - "version": "0.11.1", + "version": "0.16.0", "bin": { "create-better-t-stack": "dist/index.js", }, diff --git a/package.json b/package.json index 4517835..93b43d9 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,7 @@ "build:web:cloudflare": "bun install && bun run build:web", "build:cli": "turbo run build --filter=create-better-t-stack", "check": "turbo check", - "prepare": "husky", - "publish-packages": "turbo run build && changeset publish" + "publish-packages": "turbo run build --filter=create-better-t-stack && changeset publish" }, "devDependencies": { "@biomejs/biome": "1.9.4", @@ -28,6 +27,6 @@ "engines": { "node": ">=20" }, - "packageManager": "bun@1.2.2", + "packageManager": "bun@1.2.5", "workspaces": ["apps/*"] } diff --git a/turbo.json b/turbo.json index 02349e0..4bde692 100644 --- a/turbo.json +++ b/turbo.json @@ -10,8 +10,8 @@ "lint": { "dependsOn": ["^lint"] }, - "check-types": { - "dependsOn": ["^check-types"] + "check": { + "dependsOn": ["^check"] }, "dev": { "cache": false,