mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
Add Biome and Husky for code formatting and Git hooks
This commit is contained in:
5
.changeset/fluffy-dodos-act.md
Normal file
5
.changeset/fluffy-dodos-act.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"create-better-t-stack": minor
|
||||
---
|
||||
|
||||
Add Biome and Husky for code formatting and Git hooks
|
||||
14
.github/workflows/main.yaml
vendored
Normal file
14
.github/workflows/main.yaml
vendored
Normal file
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"tailwindStylesheet": "./src/index.css"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: () => <Loader />,
|
||||
Wrap: function WrapComponent({ children }) {
|
||||
return (
|
||||
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
</trpc.Provider>
|
||||
);
|
||||
},
|
||||
routeTree,
|
||||
defaultPreload: "intent",
|
||||
context: { trpcQueryUtils },
|
||||
defaultPendingComponent: () => <Loader />,
|
||||
Wrap: function WrapComponent({ children }) {
|
||||
return (
|
||||
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
</trpc.Provider>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 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(<RouterProvider router={router} />);
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(<RouterProvider router={router} />);
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ function HomeComponent() {
|
||||
<section className="rounded-lg border p-4">
|
||||
<h2 className="mb-2 font-medium">API Status</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
|
||||
></div>
|
||||
<div
|
||||
className={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ app.use(logger());
|
||||
app.use(
|
||||
"/*",
|
||||
cors({
|
||||
origin: process.env.CORS_ORIGIN!,
|
||||
origin: process.env.CORS_ORIGIN || "",
|
||||
allowMethods: ["GET", "POST", "OPTIONS"],
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <Loader />;
|
||||
}
|
||||
if (isPending) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto mt-10 max-w-md p-6">
|
||||
<h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
|
||||
return (
|
||||
<div className="mx-auto mt-10 max-w-md p-6">
|
||||
<h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
|
||||
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
void form.handleSubmit();
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<form.Field
|
||||
name="email"
|
||||
children={(field) => (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={field.name}>Email</Label>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
type="email"
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
/>
|
||||
{field.state.meta.errors.map((error) => (
|
||||
<p key={error?.message} className="text-red-500">
|
||||
{error?.message}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
void form.handleSubmit();
|
||||
}}
|
||||
className="space-y-4"
|
||||
>
|
||||
<div>
|
||||
<form.Field name="email">
|
||||
{(field) => (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={field.name}>Email</Label>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
type="email"
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
/>
|
||||
{field.state.meta.errors.map((error) => (
|
||||
<p key={error?.message} className="text-red-500">
|
||||
{error?.message}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<form.Field
|
||||
name="password"
|
||||
children={(field) => (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={field.name}>Password</Label>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
type="password"
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
/>
|
||||
{field.state.meta.errors.map((error) => (
|
||||
<p key={error?.message} className="text-red-500">
|
||||
{error?.message}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<form.Field name="password">
|
||||
{(field) => (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor={field.name}>Password</Label>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
type="password"
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
/>
|
||||
{field.state.meta.errors.map((error) => (
|
||||
<p key={error?.message} className="text-red-500">
|
||||
{error?.message}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</form.Field>
|
||||
</div>
|
||||
|
||||
<form.Subscribe>
|
||||
{(state) => (
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={!state.canSubmit || state.isSubmitting}
|
||||
>
|
||||
{state.isSubmitting ? "Submitting..." : "Sign In"}
|
||||
</Button>
|
||||
)}
|
||||
</form.Subscribe>
|
||||
</form>
|
||||
<form.Subscribe>
|
||||
{(state) => (
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full"
|
||||
disabled={!state.canSubmit || state.isSubmitting}
|
||||
>
|
||||
{state.isSubmitting ? "Submitting..." : "Sign In"}
|
||||
</Button>
|
||||
)}
|
||||
</form.Subscribe>
|
||||
</form>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={onSwitchToSignUp}
|
||||
className="text-indigo-600 hover:text-indigo-800"
|
||||
>
|
||||
Need an account? Sign Up
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="mt-4 text-center">
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={onSwitchToSignUp}
|
||||
className="text-indigo-600 hover:text-indigo-800"
|
||||
>
|
||||
Need an account? Sign Up
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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: () => <Loader />,
|
||||
Wrap: function WrapComponent({ children }) {
|
||||
return (
|
||||
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
</trpc.Provider>
|
||||
);
|
||||
},
|
||||
routeTree,
|
||||
defaultPreload: "intent",
|
||||
context: { trpcQueryUtils },
|
||||
defaultPendingComponent: () => <Loader />,
|
||||
Wrap: function WrapComponent({ children }) {
|
||||
return (
|
||||
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
</trpc.Provider>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 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(<RouterProvider router={router} />);
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(<RouterProvider router={router} />);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className="container mx-auto max-w-3xl px-4 py-2">
|
||||
<pre className="overflow-x-auto font-mono text-sm">{TITLE_TEXT}</pre>
|
||||
<div className="grid gap-6">
|
||||
<section className="rounded-lg border p-4">
|
||||
<h2 className="mb-2 font-medium">API Status</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
|
||||
></div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
: healthCheck.data
|
||||
? "Connected"
|
||||
: "Disconnected"}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
return (
|
||||
<div className="container mx-auto max-w-3xl px-4 py-2">
|
||||
<pre className="overflow-x-auto font-mono text-sm">{TITLE_TEXT}</pre>
|
||||
<div className="grid gap-6">
|
||||
<section className="rounded-lg border p-4">
|
||||
<h2 className="mb-2 font-medium">API Status</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{healthCheck.isLoading
|
||||
? "Checking..."
|
||||
: healthCheck.data
|
||||
? "Connected"
|
||||
: "Disconnected"}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="mb-3 font-medium">Core Features</h2>
|
||||
<ul className="grid grid-cols-2 gap-3">
|
||||
<FeatureItem
|
||||
title="Type-Safe API"
|
||||
description="End-to-end type safety with tRPC"
|
||||
/>
|
||||
<FeatureItem
|
||||
title="Modern React"
|
||||
description="TanStack Router + TanStack Query"
|
||||
/>
|
||||
<FeatureItem
|
||||
title="Fast Backend"
|
||||
description="Lightweight Hono server"
|
||||
/>
|
||||
<FeatureItem
|
||||
title="Beautiful UI"
|
||||
description="TailwindCSS + shadcn/ui components"
|
||||
/>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2 className="mb-3 font-medium">Core Features</h2>
|
||||
<ul className="grid grid-cols-2 gap-3">
|
||||
<FeatureItem
|
||||
title="Type-Safe API"
|
||||
description="End-to-end type safety with tRPC"
|
||||
/>
|
||||
<FeatureItem
|
||||
title="Modern React"
|
||||
description="TanStack Router + TanStack Query"
|
||||
/>
|
||||
<FeatureItem
|
||||
title="Fast Backend"
|
||||
description="Lightweight Hono server"
|
||||
/>
|
||||
<FeatureItem
|
||||
title="Beautiful UI"
|
||||
description="TailwindCSS + shadcn/ui components"
|
||||
/>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<div className="mt-4 flex flex-col gap-4 sm:flex-row sm:items-center">
|
||||
<Button asChild>
|
||||
<Link to="/todos" className="flex items-center">
|
||||
View Todo Demo
|
||||
<ArrowRight className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="mt-4 flex flex-col gap-4 sm:flex-row sm:items-center">
|
||||
<Button asChild>
|
||||
<Link to="/todos" className="flex items-center">
|
||||
View Todo Demo
|
||||
<ArrowRight className="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FeatureItem({
|
||||
title,
|
||||
description,
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
title: string;
|
||||
description: string;
|
||||
}) {
|
||||
return (
|
||||
<li className="border-l-2 border-primary py-1 pl-3">
|
||||
<h3 className="font-medium">{title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</li>
|
||||
);
|
||||
return (
|
||||
<li className="border-l-2 border-primary py-1 pl-3">
|
||||
<h3 className="font-medium">{title}</h3>
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
42
apps/cli/template/with-biome/biome.json
Normal file
42
apps/cli/template/with-biome/biome.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,6 @@ export default defineConfig({
|
||||
out: "./migrations",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
url: process.env.POSTGRES_URL!,
|
||||
url: process.env.POSTGRES_URL || "",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
1
apps/cli/template/with-husky/.husky/pre-commit
Normal file
1
apps/cli/template/with-husky/.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
||||
lint-staged
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false,
|
||||
"ignore": [".next", "dist", ".source", "out", "template"]
|
||||
"ignore": [".next", "dist", ".source", "out", "template", ".turbo"]
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
|
||||
2
bun.lock
2
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",
|
||||
},
|
||||
|
||||
@@ -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/*"]
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"lint": {
|
||||
"dependsOn": ["^lint"]
|
||||
},
|
||||
"check-types": {
|
||||
"dependsOn": ["^check-types"]
|
||||
"check": {
|
||||
"dependsOn": ["^check"]
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
|
||||
Reference in New Issue
Block a user