Complete monorepo toolchain setup (tasks 1.1-1.7)

- Turborepo + pnpm workspaces with catalog for shared dependency versions
- TypeScript strict config (base + per-package extends)
- Tailwind CSS v4 via @tailwindcss/vite plugin
- ESLint + Prettier shared config
- Planner app scaffolded with React Router 7 (port 3001)
- Journal app scaffolded with React Router 7 (port 3000)
- Both apps build and start successfully via turbo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-03-22 12:12:57 +01:00
parent 48547fdc82
commit 30d40de7cf
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
28 changed files with 4096 additions and 13 deletions

7
.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2
}

27
apps/journal/app/root.tsx Normal file
View file

@ -0,0 +1,27 @@
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
import type { LinksFunction } from "react-router";
import stylesheet from "@trails-cool/ui/styles.css?url";
export const links: LinksFunction = () => [{ rel: "stylesheet", href: stylesheet }];
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="min-h-screen bg-gray-50">
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}

View file

@ -0,0 +1,3 @@
import { type RouteConfig, index } from "@react-router/dev/routes";
export default [index("routes/home.tsx")] satisfies RouteConfig;

View file

@ -0,0 +1,17 @@
import type { Route } from "./+types/home";
export function meta({}: Route.MetaArgs) {
return [
{ title: "trails.cool" },
{ name: "description", content: "Your outdoor activity journal" },
];
}
export default function Home() {
return (
<div className="mx-auto max-w-4xl px-4 py-16">
<h1 className="text-4xl font-bold text-gray-900">trails.cool</h1>
<p className="mt-4 text-lg text-gray-600">Your outdoor activity journal</p>
</div>
);
}

View file

@ -4,14 +4,31 @@
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "echo 'TODO: setup journal dev'", "dev": "react-router dev",
"build": "echo 'TODO: setup journal build'" "build": "react-router build",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc"
}, },
"dependencies": { "dependencies": {
"@trails-cool/ui": "workspace:*", "@trails-cool/ui": "workspace:*",
"@trails-cool/types": "workspace:*", "@trails-cool/types": "workspace:*",
"@trails-cool/map": "workspace:*", "@trails-cool/map": "workspace:*",
"@trails-cool/gpx": "workspace:*", "@trails-cool/gpx": "workspace:*",
"@trails-cool/i18n": "workspace:*" "@trails-cool/i18n": "workspace:*",
"react": "catalog:",
"react-dom": "catalog:",
"react-router": "catalog:",
"@react-router/node": "catalog:",
"@react-router/serve": "catalog:",
"isbot": "^5.1.0"
},
"devDependencies": {
"@react-router/dev": "catalog:",
"@tailwindcss/vite": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"vite": "catalog:"
} }
} }

View file

@ -0,0 +1,5 @@
import type { Config } from "@react-router/dev/config";
export default {
ssr: true,
} satisfies Config;

View file

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"include": [
"**/*.ts",
"**/*.tsx",
".react-router/types/**/*"
],
"compilerOptions": {
"rootDirs": [".", "./.react-router/types"],
"outDir": "build",
"types": ["vite/client"]
}
}

View file

@ -0,0 +1,10 @@
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss(), reactRouter()],
server: {
port: 3000,
},
});

27
apps/planner/app/root.tsx Normal file
View file

@ -0,0 +1,27 @@
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
import type { LinksFunction } from "react-router";
import stylesheet from "@trails-cool/ui/styles.css?url";
export const links: LinksFunction = () => [{ rel: "stylesheet", href: stylesheet }];
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="h-screen w-screen overflow-hidden bg-white">
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}

View file

@ -0,0 +1,3 @@
import { type RouteConfig, index } from "@react-router/dev/routes";
export default [index("routes/home.tsx")] satisfies RouteConfig;

View file

@ -0,0 +1,19 @@
import type { Route } from "./+types/home";
export function meta({}: Route.MetaArgs) {
return [
{ title: "trails.cool Planner" },
{ name: "description", content: "Collaborative route planning" },
];
}
export default function Home() {
return (
<div className="flex h-full items-center justify-center">
<div className="text-center">
<h1 className="text-4xl font-bold text-gray-900">trails.cool Planner</h1>
<p className="mt-4 text-lg text-gray-600">Collaborative route planning</p>
</div>
</div>
);
}

View file

@ -4,14 +4,31 @@
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "echo 'TODO: setup planner dev'", "dev": "react-router dev",
"build": "echo 'TODO: setup planner build'" "build": "react-router build",
"start": "react-router-serve ./build/server/index.js",
"typecheck": "react-router typegen && tsc"
}, },
"dependencies": { "dependencies": {
"@trails-cool/ui": "workspace:*", "@trails-cool/ui": "workspace:*",
"@trails-cool/types": "workspace:*", "@trails-cool/types": "workspace:*",
"@trails-cool/map": "workspace:*", "@trails-cool/map": "workspace:*",
"@trails-cool/gpx": "workspace:*", "@trails-cool/gpx": "workspace:*",
"@trails-cool/i18n": "workspace:*" "@trails-cool/i18n": "workspace:*",
"react": "catalog:",
"react-dom": "catalog:",
"react-router": "catalog:",
"@react-router/node": "catalog:",
"@react-router/serve": "catalog:",
"isbot": "^5.1.0"
},
"devDependencies": {
"@react-router/dev": "catalog:",
"@tailwindcss/vite": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"tailwindcss": "catalog:",
"typescript": "catalog:",
"vite": "catalog:"
} }
} }

View file

@ -0,0 +1,5 @@
import type { Config } from "@react-router/dev/config";
export default {
ssr: true,
} satisfies Config;

View file

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"include": [
"**/*.ts",
"**/*.tsx",
".react-router/types/**/*"
],
"compilerOptions": {
"rootDirs": [".", "./.react-router/types"],
"outDir": "build",
"types": ["vite/client"]
}
}

View file

@ -0,0 +1,10 @@
import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [tailwindcss(), reactRouter()],
server: {
port: 3001,
},
});

10
eslint.config.js Normal file
View file

@ -0,0 +1,10 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import prettier from "eslint-config-prettier";
export default tseslint.config(
{ ignores: ["**/build/", "**/dist/", "**/.react-router/", "**/node_modules/"] },
js.configs.recommended,
...tseslint.configs.recommended,
prettier,
);

View file

@ -1,12 +1,12 @@
## 1. Monorepo Toolchain Setup ## 1. Monorepo Toolchain Setup
- [ ] 1.1 Install pnpm and Turborepo, configure workspaces - [x] 1.1 Install pnpm and Turborepo, configure workspaces
- [ ] 1.2 Set up TypeScript config (base tsconfig, per-package extends) - [x] 1.2 Set up TypeScript config (base tsconfig, per-package extends)
- [ ] 1.3 Set up Tailwind CSS (shared config, content paths for monorepo) - [x] 1.3 Set up Tailwind CSS (shared config, content paths for monorepo)
- [ ] 1.4 Set up ESLint and Prettier (shared config) - [x] 1.4 Set up ESLint and Prettier (shared config)
- [ ] 1.5 Scaffold Planner app with React Router 7 (`apps/planner`) - [x] 1.5 Scaffold Planner app with React Router 7 (`apps/planner`)
- [ ] 1.6 Scaffold Journal app with React Router 7 (`apps/journal`) - [x] 1.6 Scaffold Journal app with React Router 7 (`apps/journal`)
- [ ] 1.7 Verify `turbo dev` starts both apps and `turbo build` succeeds - [x] 1.7 Verify `turbo dev` starts both apps and `turbo build` succeeds
## 2. Shared Packages ## 2. Shared Packages

View file

@ -13,5 +13,24 @@
"lint": "turbo lint", "lint": "turbo lint",
"test": "turbo test", "test": "turbo test",
"typecheck": "turbo typecheck" "typecheck": "turbo typecheck"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@react-router/dev": "^7.13.1",
"@react-router/node": "^7.13.1",
"@react-router/serve": "^7.13.1",
"@tailwindcss/vite": "^4.2.2",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.1",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router": "^7.13.1",
"tailwindcss": "^4.2.2",
"turbo": "^2.8.20",
"typescript-eslint": "^8.57.1",
"vite": "catalog:"
} }
} }

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}

View file

@ -2,6 +2,10 @@
"name": "@trails-cool/ui", "name": "@trails-cool/ui",
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
"exports": {
".": "./src/index.ts",
"./styles.css": "./src/styles.css"
},
"main": "./src/index.ts", "main": "./src/index.ts",
"types": "./src/index.ts" "types": "./src/index.ts"
} }

View file

@ -0,0 +1 @@
@import "tailwindcss";

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
}

3782
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,17 @@
packages: packages:
- "apps/*" - "apps/*"
- "packages/*" - "packages/*"
catalog:
react: ^19.1.0
react-dom: ^19.1.0
react-router: ^7.6.1
"@react-router/dev": ^7.6.1
"@react-router/node": ^7.6.1
"@react-router/serve": ^7.6.1
"@types/react": ^19.1.4
"@types/react-dom": ^19.1.5
tailwindcss: ^4.1.7
"@tailwindcss/vite": ^4.1.7
typescript: ^5.8.3
vite: ^6.0.0

20
tsconfig.base.json Normal file
View file

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUncheckedIndexedAccess": true
},
"exclude": ["node_modules", "build", "dist"]
}