From 2c58d03f63bb2c415ce63e4e977d397ba1dcdcbc Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:41:52 +0100 Subject: [PATCH] feat: add monorepo configuration and shared packages structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root-level setup: - package.json with Turborepo scripts - pnpm-workspace.yaml for workspace management - turbo.json for build pipeline - Common config files (.nvmrc, .prettierrc, .editorconfig) Shared packages (packages/): - @manacore/shared-types - Common TypeScript types - @manacore/shared-supabase - Unified Supabase client - @manacore/shared-utils - Date, string, async utilities - @manacore/shared-ui - React Native UI components (placeholder) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .editorconfig | 18 ++++ .nvmrc | 1 + .prettierignore | 32 +++++++ .prettierrc | 26 ++++++ README.md | 124 +++++++++++++++++++++++++ package.json | 29 ++++++ packages/shared-supabase/package.json | 24 +++++ packages/shared-supabase/src/index.ts | 62 +++++++++++++ packages/shared-supabase/tsconfig.json | 17 ++++ packages/shared-types/package.json | 18 ++++ packages/shared-types/src/index.ts | 47 ++++++++++ packages/shared-types/tsconfig.json | 17 ++++ packages/shared-ui/package.json | 23 +++++ packages/shared-ui/src/index.ts | 24 +++++ packages/shared-ui/tsconfig.json | 18 ++++ packages/shared-utils/package.json | 21 +++++ packages/shared-utils/src/async.ts | 71 ++++++++++++++ packages/shared-utils/src/date.ts | 43 +++++++++ packages/shared-utils/src/index.ts | 12 +++ packages/shared-utils/src/string.ts | 43 +++++++++ packages/shared-utils/tsconfig.json | 17 ++++ pnpm-workspace.yaml | 17 ++++ turbo.json | 34 +++++++ 23 files changed, 738 insertions(+) create mode 100644 .editorconfig create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 package.json create mode 100644 packages/shared-supabase/package.json create mode 100644 packages/shared-supabase/src/index.ts create mode 100644 packages/shared-supabase/tsconfig.json create mode 100644 packages/shared-types/package.json create mode 100644 packages/shared-types/src/index.ts create mode 100644 packages/shared-types/tsconfig.json create mode 100644 packages/shared-ui/package.json create mode 100644 packages/shared-ui/src/index.ts create mode 100644 packages/shared-ui/tsconfig.json create mode 100644 packages/shared-utils/package.json create mode 100644 packages/shared-utils/src/async.ts create mode 100644 packages/shared-utils/src/date.ts create mode 100644 packages/shared-utils/src/index.ts create mode 100644 packages/shared-utils/src/string.ts create mode 100644 packages/shared-utils/tsconfig.json create mode 100644 pnpm-workspace.yaml create mode 100644 turbo.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..871cc4a14 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..209e3ef4b --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..6fef196dc --- /dev/null +++ b/.prettierignore @@ -0,0 +1,32 @@ +# Dependencies +node_modules/ +.pnpm-store/ + +# Build outputs +dist/ +build/ +.next/ +.nuxt/ +.output/ +.svelte-kit/ +.astro/ +.expo/ +android/ +ios/ + +# Turbo +.turbo/ + +# Package manager +pnpm-lock.yaml +package-lock.json +yarn.lock + +# Generated files +*.min.js +*.min.css +coverage/ + +# IDE +.idea/ +.vscode/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..c7d18100b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,26 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf", + "plugins": ["prettier-plugin-svelte", "prettier-plugin-astro"], + "overrides": [ + { + "files": "*.svelte", + "options": { + "parser": "svelte" + } + }, + { + "files": "*.astro", + "options": { + "parser": "astro" + } + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 000000000..7005ca1e1 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# Manacore Monorepo + +Monorepo containing all Manacore projects with shared packages and unified tooling. + +## Projects + +| Project | Description | Tech Stack | +|---------|-------------|------------| +| **maerchenzauber** | AI-powered story generation app | NestJS, Expo, SvelteKit, Astro | +| **manacore** | Multi-app ecosystem platform | Expo, SvelteKit, Astro | +| **manadeck** | Card/deck management app | NestJS, Expo, SvelteKit | +| **memoro** | Voice memo & AI analysis app | Expo, SvelteKit, Astro | + +## Getting Started + +### Prerequisites + +- Node.js 20+ +- pnpm 9.15.0+ + +### Installation + +```bash +# Install pnpm globally (if not installed) +npm install -g pnpm + +# Install all dependencies +pnpm install +``` + +### Development + +```bash +# Start all projects in dev mode +pnpm run dev + +# Start a specific project +pnpm run maerchenzauber:dev +pnpm run manacore:dev +pnpm run manadeck:dev +pnpm run memoro:dev + +# Build all projects +pnpm run build + +# Run tests +pnpm run test + +# Type check +pnpm run type-check + +# Format code +pnpm run format +``` + +## Shared Packages + +Located in `packages/`: + +| Package | Description | +|---------|-------------| +| `@manacore/shared-types` | Common TypeScript types | +| `@manacore/shared-supabase` | Unified Supabase client | +| `@manacore/shared-utils` | Utility functions (date, string, async) | +| `@manacore/shared-ui` | React Native UI components | + +### Using Shared Packages + +```typescript +// In any project +import { User, ApiResponse } from '@manacore/shared-types'; +import { createSupabaseClient } from '@manacore/shared-supabase'; +import { formatDate, truncate, retry } from '@manacore/shared-utils'; +``` + +## Repository Structure + +``` +manacore-monorepo/ +├── packages/ # Shared packages +│ ├── shared-types/ # TypeScript types +│ ├── shared-supabase/ # Supabase utilities +│ ├── shared-utils/ # Common utilities +│ └── shared-ui/ # React Native components +├── maerchenzauber/ # Storyteller project +├── manacore/ # Manacore apps project +├── manadeck/ # ManaDeck project +├── memoro/ # Memoro project +├── turbo.json # Turborepo configuration +├── pnpm-workspace.yaml # Workspace configuration +└── package.json # Root package +``` + +## Tooling + +- **Package Manager:** pnpm 9.15.0 +- **Build System:** Turborepo +- **Formatting:** Prettier +- **Node Version:** 20 (see .nvmrc) + +## Adding Dependencies + +```bash +# Add to root (dev tools) +pnpm add -D -w + +# Add to specific project +pnpm add --filter maerchenzauber + +# Add to shared package +pnpm add --filter @manacore/shared-utils +``` + +## Contributing + +1. Create a feature branch +2. Make changes +3. Run `pnpm run format` and `pnpm run type-check` +4. Commit with conventional commit messages +5. Create pull request + +## License + +Private - All rights reserved diff --git a/package.json b/package.json new file mode 100644 index 000000000..d2bc0a8f6 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "manacore-monorepo", + "version": "1.0.0", + "private": true, + "description": "Manacore Monorepo containing maerchenzauber, manacore, manadeck, and memoro", + "scripts": { + "dev": "turbo run dev", + "build": "turbo run build", + "test": "turbo run test", + "lint": "turbo run lint", + "type-check": "turbo run type-check", + "clean": "turbo run clean", + "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"", + "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"", + "maerchenzauber:dev": "turbo run dev --filter=maerchenzauber...", + "manacore:dev": "turbo run dev --filter=manacore...", + "manadeck:dev": "turbo run dev --filter=manadeck...", + "memoro:dev": "turbo run dev --filter=memoro..." + }, + "devDependencies": { + "prettier": "^3.3.3", + "turbo": "^2.3.0", + "typescript": "^5.9.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "packageManager": "pnpm@9.15.0" +} diff --git a/packages/shared-supabase/package.json b/packages/shared-supabase/package.json new file mode 100644 index 000000000..30c2b5e75 --- /dev/null +++ b/packages/shared-supabase/package.json @@ -0,0 +1,24 @@ +{ + "name": "@manacore/shared-supabase", + "version": "0.1.0", + "private": true, + "description": "Shared Supabase client and utilities for Manacore monorepo", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "type-check": "tsc --noEmit", + "clean": "rm -rf dist" + }, + "dependencies": { + "@supabase/supabase-js": "^2.81.1" + }, + "devDependencies": { + "typescript": "^5.9.2" + }, + "peerDependencies": { + "@manacore/shared-types": "workspace:*" + } +} diff --git a/packages/shared-supabase/src/index.ts b/packages/shared-supabase/src/index.ts new file mode 100644 index 000000000..02f068efb --- /dev/null +++ b/packages/shared-supabase/src/index.ts @@ -0,0 +1,62 @@ +/** + * Shared Supabase utilities for Manacore monorepo + * + * This package provides a unified Supabase client and common database utilities. + */ + +import { createClient, SupabaseClient } from '@supabase/supabase-js'; +import type { SupabaseConfig } from '@manacore/shared-types'; + +export { SupabaseClient } from '@supabase/supabase-js'; + +/** + * Create a Supabase client with the given configuration + */ +export function createSupabaseClient(config: SupabaseConfig): SupabaseClient { + return createClient(config.url, config.anonKey, { + auth: { + persistSession: true, + autoRefreshToken: true, + }, + }); +} + +/** + * Create a Supabase admin client with service role key + */ +export function createSupabaseAdminClient(config: SupabaseConfig): SupabaseClient { + if (!config.serviceRoleKey) { + throw new Error('Service role key is required for admin client'); + } + + return createClient(config.url, config.serviceRoleKey, { + auth: { + persistSession: false, + autoRefreshToken: false, + }, + }); +} + +/** + * Common database query helpers + */ +export const dbHelpers = { + /** + * Handle Supabase query result and return standardized response + */ + handleQueryResult(result: { data: T | null; error: any }): { + data: T | null; + error: { message: string; code?: string } | null; + } { + if (result.error) { + return { + data: null, + error: { + message: result.error.message, + code: result.error.code, + }, + }; + } + return { data: result.data, error: null }; + }, +}; diff --git a/packages/shared-supabase/tsconfig.json b/packages/shared-supabase/tsconfig.json new file mode 100644 index 000000000..112dcabe1 --- /dev/null +++ b/packages/shared-supabase/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/shared-types/package.json b/packages/shared-types/package.json new file mode 100644 index 000000000..e9e629b5b --- /dev/null +++ b/packages/shared-types/package.json @@ -0,0 +1,18 @@ +{ + "name": "@manacore/shared-types", + "version": "0.1.0", + "private": true, + "description": "Shared TypeScript types for Manacore monorepo", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "type-check": "tsc --noEmit", + "clean": "rm -rf dist" + }, + "devDependencies": { + "typescript": "^5.9.2" + } +} diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts new file mode 100644 index 000000000..597c83fb5 --- /dev/null +++ b/packages/shared-types/src/index.ts @@ -0,0 +1,47 @@ +/** + * Shared types for Manacore monorepo + * + * This package contains common TypeScript types used across all projects. + */ + +// Common user types +export interface User { + id: string; + email: string; + created_at: string; + updated_at: string; +} + +// Common API response types +export interface ApiResponse { + data: T | null; + error: ApiError | null; +} + +export interface ApiError { + message: string; + code?: string; + status?: number; +} + +// Pagination types +export interface PaginationParams { + page?: number; + limit?: number; + offset?: number; +} + +export interface PaginatedResponse { + data: T[]; + total: number; + page: number; + limit: number; + hasMore: boolean; +} + +// Supabase common types +export interface SupabaseConfig { + url: string; + anonKey: string; + serviceRoleKey?: string; +} diff --git a/packages/shared-types/tsconfig.json b/packages/shared-types/tsconfig.json new file mode 100644 index 000000000..112dcabe1 --- /dev/null +++ b/packages/shared-types/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/shared-ui/package.json b/packages/shared-ui/package.json new file mode 100644 index 000000000..1b28649be --- /dev/null +++ b/packages/shared-ui/package.json @@ -0,0 +1,23 @@ +{ + "name": "@manacore/shared-ui", + "version": "0.1.0", + "private": true, + "description": "Shared React Native UI components for Manacore monorepo", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "type-check": "tsc --noEmit", + "clean": "rm -rf dist" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-native": ">=0.70.0" + }, + "devDependencies": { + "@types/react": "^18.3.0", + "typescript": "^5.9.2" + } +} diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts new file mode 100644 index 000000000..7e98f0999 --- /dev/null +++ b/packages/shared-ui/src/index.ts @@ -0,0 +1,24 @@ +/** + * Shared React Native UI components for Manacore monorepo + * + * This package will contain common UI components used across all mobile apps. + * + * Planned components: + * - Button + * - Text + * - Input + * - Card + * - Modal + * - Loading indicators + * - Icons + */ + +// Placeholder export until components are migrated +export const SHARED_UI_VERSION = '0.1.0'; + +// Future exports will include: +// export { Button } from './components/Button'; +// export { Text } from './components/Text'; +// export { Input } from './components/Input'; +// export { Card } from './components/Card'; +// export { Modal } from './components/Modal'; diff --git a/packages/shared-ui/tsconfig.json b/packages/shared-ui/tsconfig.json new file mode 100644 index 000000000..5d80029e9 --- /dev/null +++ b/packages/shared-ui/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020"], + "jsx": "react-native", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/shared-utils/package.json b/packages/shared-utils/package.json new file mode 100644 index 000000000..3d952acb5 --- /dev/null +++ b/packages/shared-utils/package.json @@ -0,0 +1,21 @@ +{ + "name": "@manacore/shared-utils", + "version": "0.1.0", + "private": true, + "description": "Shared utility functions for Manacore monorepo", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "type-check": "tsc --noEmit", + "clean": "rm -rf dist" + }, + "dependencies": { + "date-fns": "^4.1.0" + }, + "devDependencies": { + "typescript": "^5.9.2" + } +} diff --git a/packages/shared-utils/src/async.ts b/packages/shared-utils/src/async.ts new file mode 100644 index 000000000..88f22803d --- /dev/null +++ b/packages/shared-utils/src/async.ts @@ -0,0 +1,71 @@ +/** + * Async utility functions + */ + +/** + * Sleep for a specified number of milliseconds + */ +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * Retry a function with exponential backoff + */ +export async function retry( + fn: () => Promise, + options: { + maxAttempts?: number; + initialDelay?: number; + maxDelay?: number; + backoffMultiplier?: number; + } = {} +): Promise { + const { + maxAttempts = 3, + initialDelay = 1000, + maxDelay = 10000, + backoffMultiplier = 2, + } = options; + + let lastError: Error | undefined; + let delay = initialDelay; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + return await fn(); + } catch (error) { + lastError = error as Error; + + if (attempt === maxAttempts) { + break; + } + + await sleep(delay); + delay = Math.min(delay * backoffMultiplier, maxDelay); + } + } + + throw lastError; +} + +/** + * Debounce a function + */ +export function debounce any>( + fn: T, + delay: number +): (...args: Parameters) => void { + let timeoutId: NodeJS.Timeout | null = null; + + return (...args: Parameters) => { + if (timeoutId) { + clearTimeout(timeoutId); + } + + timeoutId = setTimeout(() => { + fn(...args); + timeoutId = null; + }, delay); + }; +} diff --git a/packages/shared-utils/src/date.ts b/packages/shared-utils/src/date.ts new file mode 100644 index 000000000..8f47c29fe --- /dev/null +++ b/packages/shared-utils/src/date.ts @@ -0,0 +1,43 @@ +/** + * Date utility functions + */ + +import { format, formatDistanceToNow, parseISO } from 'date-fns'; +import { de, enUS } from 'date-fns/locale'; + +const locales = { + de, + en: enUS, +}; + +type LocaleKey = keyof typeof locales; + +/** + * Format a date string to a readable format + */ +export function formatDate( + date: string | Date, + formatStr: string = 'PPP', + locale: LocaleKey = 'de' +): string { + const dateObj = typeof date === 'string' ? parseISO(date) : date; + return format(dateObj, formatStr, { locale: locales[locale] }); +} + +/** + * Get relative time from now (e.g., "2 hours ago") + */ +export function formatRelativeTime(date: string | Date, locale: LocaleKey = 'de'): string { + const dateObj = typeof date === 'string' ? parseISO(date) : date; + return formatDistanceToNow(dateObj, { + addSuffix: true, + locale: locales[locale], + }); +} + +/** + * Format a date for API requests (ISO string) + */ +export function toISOString(date: Date): string { + return date.toISOString(); +} diff --git a/packages/shared-utils/src/index.ts b/packages/shared-utils/src/index.ts new file mode 100644 index 000000000..b18b8d210 --- /dev/null +++ b/packages/shared-utils/src/index.ts @@ -0,0 +1,12 @@ +/** + * Shared utility functions for Manacore monorepo + */ + +// Date utilities +export * from './date'; + +// String utilities +export * from './string'; + +// Async utilities +export * from './async'; diff --git a/packages/shared-utils/src/string.ts b/packages/shared-utils/src/string.ts new file mode 100644 index 000000000..711206225 --- /dev/null +++ b/packages/shared-utils/src/string.ts @@ -0,0 +1,43 @@ +/** + * String utility functions + */ + +/** + * Truncate a string to a maximum length with ellipsis + */ +export function truncate(str: string, maxLength: number): string { + if (str.length <= maxLength) return str; + return str.slice(0, maxLength - 3) + '...'; +} + +/** + * Capitalize the first letter of a string + */ +export function capitalize(str: string): string { + if (!str) return str; + return str.charAt(0).toUpperCase() + str.slice(1); +} + +/** + * Generate a random string ID + */ +export function generateId(length: number = 8): string { + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +/** + * Slugify a string for URLs + */ +export function slugify(str: string): string { + return str + .toLowerCase() + .trim() + .replace(/[^\w\s-]/g, '') + .replace(/[\s_-]+/g, '-') + .replace(/^-+|-+$/g, ''); +} diff --git a/packages/shared-utils/tsconfig.json b/packages/shared-utils/tsconfig.json new file mode 100644 index 000000000..112dcabe1 --- /dev/null +++ b/packages/shared-utils/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "outDir": "./dist" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 000000000..a4d5303b2 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,17 @@ +packages: + # Main projects + - 'maerchenzauber' + - 'manacore' + - 'manadeck' + - 'memoro' + + # Sub-apps within projects + - 'maerchenzauber/apps/*' + - 'maerchenzauber/packages/*' + - 'manacore/apps/*' + - 'manadeck/apps/*' + - 'manadeck/backend' + - 'memoro/apps/*' + + # Future shared packages + - 'packages/*' diff --git a/turbo.json b/turbo.json new file mode 100644 index 000000000..2d1d4f88f --- /dev/null +++ b/turbo.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "dev": { + "cache": false, + "persistent": true + }, + "build": { + "dependsOn": ["^build"], + "outputs": [ + "dist/**", + "build/**", + ".next/**", + ".svelte-kit/**", + ".astro/**" + ] + }, + "lint": { + "dependsOn": ["^lint"], + "outputs": [] + }, + "type-check": { + "dependsOn": ["^type-check"], + "outputs": [] + }, + "test": { + "dependsOn": ["^build"], + "outputs": ["coverage/**"] + }, + "clean": { + "cache": false + } + } +}