From 8de629dd2d1dbc3a8b4fc067e310c34b2f5764a8 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 12:53:42 +0100
Subject: [PATCH 01/34] =?UTF-8?q?=F0=9F=9A=80=20ci:=20add=20dev=20branch?=
=?UTF-8?q?=20workflow=20with=20PR=20validation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Rename ci-main.yml to ci.yml for clarity
- Add PR-based validation (type-check, lint) for dev and main branches
- Add path filtering to skip CI on docs-only changes
- Trigger staging deployment only on push to dev branch
- Keep production deployment manual with confirmation
---
.github/workflows/cd-production.yml | 7 +++
.github/workflows/cd-staging.yml | 14 +++--
.github/workflows/{ci-main.yml => ci.yml} | 72 ++++++++++++++++++++---
3 files changed, 80 insertions(+), 13 deletions(-)
rename .github/workflows/{ci-main.yml => ci.yml} (60%)
diff --git a/.github/workflows/cd-production.yml b/.github/workflows/cd-production.yml
index 9c98e5982..ec614d4bb 100644
--- a/.github/workflows/cd-production.yml
+++ b/.github/workflows/cd-production.yml
@@ -1,3 +1,10 @@
+# Production Deployment
+#
+# Triggered by:
+# - Manual only (workflow_dispatch with confirmation)
+#
+# Flow: dev (staging) → main (production)
+# Requires typing "deploy" to confirm
name: CD - Production Deployment
on:
diff --git a/.github/workflows/cd-staging.yml b/.github/workflows/cd-staging.yml
index 6b0e52d80..8c6e83955 100644
--- a/.github/workflows/cd-staging.yml
+++ b/.github/workflows/cd-staging.yml
@@ -1,11 +1,13 @@
-# Simplified staging config: mana-core-auth + chat-backend only
+# Staging Deployment
+#
+# Triggered by:
+# - Automatic: Push to dev branch (via ci.yml)
+# - Manual: workflow_dispatch
+#
# Full config archived at: .github/workflows/cd-staging.full.yml
#
-# To restore full config:
-# cp .github/workflows/cd-staging.full.yml .github/workflows/cd-staging.yml
-#
-# To add a service back:
-# 1. Add service to workflow_dispatch options (line ~10)
+# To add a service:
+# 1. Add service to workflow_dispatch options
# 2. Add health check in "Run health checks" step
# 3. Add service to docker-compose.staging.yml
name: CD - Staging Deployment
diff --git a/.github/workflows/ci-main.yml b/.github/workflows/ci.yml
similarity index 60%
rename from .github/workflows/ci-main.yml
rename to .github/workflows/ci.yml
index 6ffe6f93c..b9059055b 100644
--- a/.github/workflows/ci-main.yml
+++ b/.github/workflows/ci.yml
@@ -1,25 +1,82 @@
-# MINIMAL: Only builds mana-core-auth + chat Docker images, no validation
-# Full config archived at: .github/workflows/ci-main.full.yml
+# CI Pipeline: Validates code on PRs, builds and deploys on push to protected branches
#
-# To restore: cp .github/workflows/ci-main.full.yml .github/workflows/ci-main.yml
+# Flow:
+# PR → dev/main : Runs validation (required status check)
+# Push → dev : Builds images + deploys to staging
+# Push → main : Builds images (production deploy is manual)
+#
+# Full config archived at: .github/workflows/ci-main.full.yml
-name: CI - Main Branch
+name: CI
on:
push:
branches:
- main
+ - dev
+ paths:
+ - 'apps/**'
+ - 'packages/**'
+ - 'services/**'
+ - 'package.json'
+ - 'pnpm-lock.yaml'
+ - 'turbo.json'
+ pull_request:
+ branches:
+ - main
+ - dev
+ paths:
+ - 'apps/**'
+ - 'packages/**'
+ - 'services/**'
+ - 'package.json'
+ - 'pnpm-lock.yaml'
+ - 'turbo.json'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
+env:
+ NODE_VERSION: '20'
+ PNPM_VERSION: '9.15.0'
+
jobs:
- # Build Docker images directly - Dockerfiles handle their own dependencies
+ # Validation job - runs on PRs to catch issues before merge
+ validate:
+ name: Validate
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v2
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'pnpm'
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Type check
+ run: pnpm run type-check
+
+ - name: Lint
+ run: pnpm run lint || echo "Lint warnings found"
+
+ # Build Docker images - only on push to dev/main (not PRs)
build-docker-images:
name: Build ${{ matrix.service.name }}
runs-on: ubuntu-latest
+ if: github.event_name == 'push'
strategy:
matrix:
service:
@@ -73,11 +130,12 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
- # Trigger staging deployment after all images are built
+ # Trigger staging deployment after all images are built (only on push to dev)
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build-docker-images
+ if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
steps:
- name: Trigger staging deployment
uses: actions/github-script@v7
@@ -87,6 +145,6 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'cd-staging.yml',
- ref: 'main'
+ ref: 'dev'
});
console.log('Staging deployment triggered');
From e423785a2078433176f8673d62f58f2a4c95e3ae Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 12:56:06 +0100
Subject: [PATCH 02/34] =?UTF-8?q?=F0=9F=94=A7=20ci:=20remove=20auto-deploy?=
=?UTF-8?q?,=20keep=20manual/tag-based=20only?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/ci.yml | 27 ++++++---------------------
1 file changed, 6 insertions(+), 21 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b9059055b..f30b9b10a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,9 +1,12 @@
-# CI Pipeline: Validates code on PRs, builds and deploys on push to protected branches
+# CI Pipeline: Validates code on PRs, builds images on push
#
# Flow:
# PR → dev/main : Runs validation (required status check)
-# Push → dev : Builds images + deploys to staging
-# Push → main : Builds images (production deploy is manual)
+# Push → dev/main : Builds Docker images (NO auto-deploy)
+#
+# Deployments are triggered separately:
+# - Manual: workflow_dispatch on cd-staging.yml / cd-production.yml
+# - Tag-based: push tag like "chat-staging-v1.0.0" triggers cd-staging-tagged.yml
#
# Full config archived at: .github/workflows/ci-main.full.yml
@@ -130,21 +133,3 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
- # Trigger staging deployment after all images are built (only on push to dev)
- deploy-staging:
- name: Deploy to Staging
- runs-on: ubuntu-latest
- needs: build-docker-images
- if: github.event_name == 'push' && github.ref == 'refs/heads/dev'
- steps:
- - name: Trigger staging deployment
- uses: actions/github-script@v7
- with:
- script: |
- await github.rest.actions.createWorkflowDispatch({
- owner: context.repo.owner,
- repo: context.repo.repo,
- workflow_id: 'cd-staging.yml',
- ref: 'dev'
- });
- console.log('Staging deployment triggered');
From 67a15cc9eab3091b96090ecee669895b77e0902c Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 14:38:18 +0100
Subject: [PATCH 03/34] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20dx:?=
=?UTF-8?q?=20add=20automatic=20database=20setup=20and=20dev:*:full=20comm?=
=?UTF-8?q?ands?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add scripts/setup-databases.sh for automatic DB creation and schema push
- Add dev:*:full commands (chat, zitare, contacts, calendar, clock, todo, picture)
- Update docker/init-db to create all databases on first startup
- Add docs/LOCAL_DEVELOPMENT.md with comprehensive local dev guide
- Update CLAUDE.md with new quick start commands
Now developers can run `pnpm dev:chat:full` to automatically:
1. Create the database if missing
2. Push the latest schema
3. Start auth, backend, and web with colored output
---
CLAUDE.md | 38 +++-
docker/init-db/01-create-databases.sql | 44 ++--
docs/LOCAL_DEVELOPMENT.md | 275 +++++++++++++++++++++++++
package.json | 11 +-
scripts/setup-databases.sh | 169 +++++++++++++++
5 files changed, 517 insertions(+), 20 deletions(-)
create mode 100644 docs/LOCAL_DEVELOPMENT.md
create mode 100755 scripts/setup-databases.sh
diff --git a/CLAUDE.md b/CLAUDE.md
index 229a8a5c2..9ed990542 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -60,19 +60,46 @@ These projects are temporarily archived and excluded from the workspace. To re-a
## Development Commands
-```bash
-# Install dependencies
-pnpm install
+For detailed local development setup, see **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)**.
+### Quick Start (Recommended)
+
+Use `dev:*:full` commands to start any app with automatic database setup:
+
+```bash
+pnpm docker:up # Start PostgreSQL, Redis, MinIO
+pnpm dev:chat:full # Start chat with auth + auto DB setup
+pnpm dev:zitare:full # Start zitare with auth + auto DB setup
+pnpm dev:contacts:full # Start contacts with auth + auto DB setup
+pnpm dev:calendar:full # Start calendar with auth + auto DB setup
+pnpm dev:clock:full # Start clock with auth + auto DB setup
+pnpm dev:todo:full # Start todo with auth + auto DB setup
+pnpm dev:picture:full # Start picture with auth + auto DB setup
+```
+
+These commands automatically:
+1. Create the database if missing
+2. Push the latest schema
+3. Start auth, backend, and web with colored output
+
+### Database Setup
+
+```bash
+pnpm setup:db # Setup ALL databases and schemas
+pnpm setup:db:chat # Setup just chat
+pnpm setup:db:auth # Setup just auth
+```
+
+### Individual App Commands
+
+```bash
# Start specific project (runs all apps in project)
pnpm run manacore:dev
pnpm run manadeck:dev
pnpm run picture:dev
pnpm run chat:dev
pnpm run zitare:dev
-pnpm run presi:dev
pnpm run contacts:dev
-pnpm run mail:dev
# Start specific app within project
pnpm run dev:chat:mobile # Just mobile app
@@ -607,6 +634,7 @@ PORT=...
## Project-Specific Documentation
+- **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)** - Database setup and `dev:*:full` commands
- **[docs/ENVIRONMENT_VARIABLES.md](docs/ENVIRONMENT_VARIABLES.md)** - Complete environment setup guide
Each project has its own `CLAUDE.md` with detailed information:
diff --git a/docker/init-db/01-create-databases.sql b/docker/init-db/01-create-databases.sql
index 428bb33d8..b22fe3cfb 100644
--- a/docker/init-db/01-create-databases.sql
+++ b/docker/init-db/01-create-databases.sql
@@ -1,21 +1,37 @@
-- Create databases for all services
-- This script runs on first container initialization
--- Create chat database
-CREATE DATABASE chat;
-
--- Create voxel_lava database
-CREATE DATABASE voxel_lava;
-
--- Create storage database (cloud drive)
-CREATE DATABASE storage;
-
--- Create todo database
-CREATE DATABASE todo;
+-- Core databases
+CREATE DATABASE IF NOT EXISTS chat;
+CREATE DATABASE IF NOT EXISTS zitare;
+CREATE DATABASE IF NOT EXISTS contacts;
+CREATE DATABASE IF NOT EXISTS calendar;
+CREATE DATABASE IF NOT EXISTS clock;
+CREATE DATABASE IF NOT EXISTS todo;
+CREATE DATABASE IF NOT EXISTS manadeck;
+CREATE DATABASE IF NOT EXISTS storage;
+CREATE DATABASE IF NOT EXISTS mail;
+CREATE DATABASE IF NOT EXISTS moodlit;
+CREATE DATABASE IF NOT EXISTS finance;
+CREATE DATABASE IF NOT EXISTS inventory;
+CREATE DATABASE IF NOT EXISTS techbase;
+CREATE DATABASE IF NOT EXISTS voxel_lava;
+CREATE DATABASE IF NOT EXISTS figgos;
-- Grant all privileges to the default user
GRANT ALL PRIVILEGES ON DATABASE chat TO manacore;
-GRANT ALL PRIVILEGES ON DATABASE voxel_lava TO manacore;
-GRANT ALL PRIVILEGES ON DATABASE manacore TO manacore;
-GRANT ALL PRIVILEGES ON DATABASE storage TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE zitare TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE contacts TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE calendar TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE clock TO manacore;
GRANT ALL PRIVILEGES ON DATABASE todo TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE manadeck TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE storage TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE mail TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE moodlit TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE finance TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE inventory TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE techbase TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE voxel_lava TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE figgos TO manacore;
+GRANT ALL PRIVILEGES ON DATABASE manacore TO manacore;
diff --git a/docs/LOCAL_DEVELOPMENT.md b/docs/LOCAL_DEVELOPMENT.md
new file mode 100644
index 000000000..1647adc02
--- /dev/null
+++ b/docs/LOCAL_DEVELOPMENT.md
@@ -0,0 +1,275 @@
+# Local Development Guide
+
+This guide explains how to set up and run applications locally with automatic database setup.
+
+## Quick Start
+
+For any project with a backend, use the `dev:*:full` command:
+
+```bash
+pnpm dev:chat:full # Start chat with auth + database setup
+pnpm dev:zitare:full # Start zitare with auth + database setup
+pnpm dev:contacts:full # Start contacts with auth + database setup
+# ... etc
+```
+
+These commands automatically:
+1. Create the database if it doesn't exist
+2. Push the latest schema (Drizzle `db:push`)
+3. Start the auth service (mana-core-auth)
+4. Start the backend and web app with colored output
+
+## Available Full Dev Commands
+
+| Command | Database | Backend Port | Web Port |
+|---------|----------|--------------|----------|
+| `pnpm dev:chat:full` | chat | 3002 | 5173 |
+| `pnpm dev:zitare:full` | zitare | 3007 | 5177 |
+| `pnpm dev:contacts:full` | contacts | 3015 | 5184 |
+| `pnpm dev:calendar:full` | calendar | 3014 | 5179 |
+| `pnpm dev:clock:full` | clock | 3017 | 5187 |
+| `pnpm dev:todo:full` | todo | 3018 | 5188 |
+| `pnpm dev:picture:full` | picture | 3006 | 5175 |
+
+## Prerequisites
+
+Before running any `dev:*:full` command:
+
+```bash
+# 1. Start Docker infrastructure (PostgreSQL, Redis, MinIO)
+pnpm docker:up
+
+# 2. Generate environment files (runs automatically on pnpm install)
+pnpm setup:env
+```
+
+## Database Setup Commands
+
+### Individual Service Setup
+
+```bash
+pnpm setup:db:auth # Setup mana-core-auth database + schema
+pnpm setup:db:chat # Setup chat database + schema
+pnpm setup:db:zitare # Setup zitare database + schema
+pnpm setup:db:contacts # Setup contacts database + schema
+pnpm setup:db:calendar # Setup calendar database + schema
+pnpm setup:db:clock # Setup clock database + schema
+pnpm setup:db:todo # Setup todo database + schema
+pnpm setup:db:picture # Setup picture database + schema
+```
+
+### Setup All Databases
+
+```bash
+pnpm setup:db # Creates ALL databases and pushes ALL schemas
+```
+
+This is useful when setting up a fresh environment or after pulling new schema changes.
+
+## How It Works
+
+### Docker Init Script
+
+On first `pnpm docker:up`, the PostgreSQL container runs `docker/init-db/01-create-databases.sql` which creates all databases:
+
+- manacore, chat, zitare, contacts, calendar, clock, todo, manadeck
+- storage, mail, moodlit, finance, inventory, techbase, voxel_lava, figgos
+
+### Setup Script
+
+The `scripts/setup-databases.sh` script:
+
+1. **Creates database** if it doesn't exist (using `psql`)
+2. **Pushes schema** using `drizzle-kit push --force`
+
+The `--force` flag auto-approves schema changes without interactive prompts.
+
+## Troubleshooting
+
+### Database doesn't exist
+
+If you see `database "xxx" does not exist`:
+
+```bash
+# Option 1: Run the setup script
+pnpm setup:db:chat # or whichever service
+
+# Option 2: Create manually
+PGPASSWORD=devpassword psql -h localhost -U manacore -d postgres -c "CREATE DATABASE chat;"
+```
+
+### Schema out of date
+
+If you see errors about missing tables/columns:
+
+```bash
+# Push the latest schema
+pnpm --filter @chat/backend db:push --force
+```
+
+### Port already in use
+
+If auth (port 3001) is already running:
+
+```bash
+# Check what's using the port
+lsof -i :3001
+
+# Kill the process if needed
+kill
+```
+
+### Fresh Start (Nuclear Option)
+
+To completely reset all databases:
+
+```bash
+# Stop and remove all containers + volumes
+pnpm docker:clean
+
+# Start fresh
+pnpm docker:up
+
+# Setup all databases
+pnpm setup:db
+```
+
+## Apps Without Full Commands
+
+Some apps don't have backends or don't use Drizzle:
+
+| App | Reason |
+|-----|--------|
+| manacore | No backend (uses other services) |
+| manadeck | Backend exists but no db:push |
+| bauntown, context, maerchenzauber, memoro, news, nutriphi, presi, quote, reader, storage, wisekeep | No backends |
+
+For these, use the regular dev commands:
+
+```bash
+pnpm dev:manacore:web
+pnpm dev:manadeck:app
+```
+
+## Adding a New Application
+
+### Step 1: Create Project Structure
+
+Create the standard project structure under `apps/`:
+
+```
+apps/newproject/
+├── apps/
+│ ├── backend/ # NestJS API (if needed)
+│ ├── mobile/ # Expo React Native app
+│ ├── web/ # SvelteKit web app
+│ └── landing/ # Astro marketing page
+├── packages/ # Project-specific shared code
+├── package.json # Workspace root
+├── pnpm-workspace.yaml
+└── CLAUDE.md # Project documentation
+```
+
+### Step 2: Configure Backend Database (if applicable)
+
+If your backend uses Drizzle ORM:
+
+1. **Add database to Docker init** (`docker/init-db/01-create-databases.sql`):
+ ```sql
+ CREATE DATABASE IF NOT EXISTS newproject;
+ GRANT ALL PRIVILEGES ON DATABASE newproject TO manacore;
+ ```
+
+2. **Add to setup script** (`scripts/setup-databases.sh`):
+
+ In the `setup_service()` function, add a new case:
+ ```bash
+ newproject)
+ create_db_if_not_exists "newproject"
+ push_schema "@newproject/backend" "newproject"
+ ;;
+ ```
+
+ Also add to the `ALL_DATABASES` array:
+ ```bash
+ ALL_DATABASES=(
+ ...
+ "newproject"
+ )
+ ```
+
+ And to the services loop at the bottom:
+ ```bash
+ for service in auth chat ... newproject; do
+ ```
+
+3. **Add DATABASE_URL to `.env.development`**:
+ ```env
+ NEWPROJECT_DATABASE_URL=postgresql://manacore:devpassword@localhost:5432/newproject
+ ```
+
+4. **Update `scripts/generate-env.mjs`** to generate the backend `.env` file.
+
+### Step 3: Add Package.json Scripts
+
+Add to root `package.json`:
+
+```json
+{
+ "scripts": {
+ // Project-level dev (all apps)
+ "newproject:dev": "turbo run dev --filter=newproject...",
+
+ // Individual app commands
+ "dev:newproject:web": "pnpm --filter @newproject/web dev",
+ "dev:newproject:mobile": "pnpm --filter @newproject/mobile dev",
+ "dev:newproject:backend": "pnpm --filter @newproject/backend dev",
+ "dev:newproject:landing": "pnpm --filter @newproject/landing dev",
+ "dev:newproject:app": "turbo run dev --filter=@newproject/web --filter=@newproject/backend",
+
+ // Full dev with auto database setup
+ "dev:newproject:full": "./scripts/setup-databases.sh newproject && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:newproject:backend\" \"pnpm dev:newproject:web\"",
+
+ // Database shortcuts
+ "newproject:db:push": "pnpm --filter @newproject/backend db:push",
+ "newproject:db:studio": "pnpm --filter @newproject/backend db:studio",
+
+ // Setup shortcut
+ "setup:db:newproject": "./scripts/setup-databases.sh newproject"
+ }
+}
+```
+
+### Step 4: Create Project CLAUDE.md
+
+Create `apps/newproject/CLAUDE.md` with:
+- Project overview
+- Structure diagram
+- Available commands
+- API endpoints (if backend)
+- Environment variables
+- Tech stack details
+
+See existing projects like `apps/chat/CLAUDE.md` for reference.
+
+### Step 5: Test the Setup
+
+```bash
+# Create database and push schema
+pnpm setup:db:newproject
+
+# Start with full dev command
+pnpm dev:newproject:full
+```
+
+## Checklist for New Projects
+
+- [ ] Create project structure under `apps/newproject/`
+- [ ] Add `pnpm-workspace.yaml` in project root
+- [ ] Add database to `docker/init-db/01-create-databases.sql`
+- [ ] Add service to `scripts/setup-databases.sh`
+- [ ] Add DATABASE_URL to `.env.development`
+- [ ] Update `scripts/generate-env.mjs` for env generation
+- [ ] Add scripts to root `package.json`
+- [ ] Create `CLAUDE.md` with project documentation
+- [ ] Test with `pnpm dev:newproject:full`
diff --git a/package.json b/package.json
index 0fdf5c01d..232bddfbf 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,9 @@
"format": "prettier --config .prettierrc.json --write \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
"format:check": "prettier --config .prettierrc.json --check \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",
"setup:env": "node scripts/generate-env.mjs",
+ "setup:db": "./scripts/setup-databases.sh",
+ "setup:db:chat": "./scripts/setup-databases.sh chat",
+ "setup:db:auth": "./scripts/setup-databases.sh auth",
"build:packages": "pnpm --filter '@manacore/*' build",
"postinstall": "node scripts/generate-env.mjs || true && pnpm run build:packages || true",
"manacore:dev": "turbo run dev --filter=manacore...",
@@ -39,25 +42,28 @@
"dev:picture:mobile": "pnpm --filter @picture/mobile dev",
"dev:picture:backend": "pnpm --filter @picture/backend dev",
"dev:picture:app": "turbo run dev --filter=@picture/web --filter=@picture/backend",
+ "dev:picture:full": "./scripts/setup-databases.sh picture && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:picture:backend\" \"pnpm dev:picture:web\"",
"dev:chat:mobile": "pnpm --filter @chat/mobile dev",
"dev:chat:web": "pnpm --filter @chat/web dev",
"dev:chat:landing": "pnpm --filter @chat/landing dev",
"dev:chat:backend": "pnpm --filter @chat/backend start:dev",
"dev:chat:app": "turbo run dev --filter=@chat/web --filter=@chat/backend",
"dev:auth": "pnpm --filter mana-core-auth start:dev",
- "dev:chat:full": "concurrently \"pnpm dev:auth\" \"pnpm dev:chat:backend\"",
+ "dev:chat:full": "./scripts/setup-databases.sh chat && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:chat:backend\" \"pnpm dev:chat:web\"",
"zitare:dev": "turbo run dev --filter=zitare...",
"dev:zitare:mobile": "pnpm --filter @zitare/mobile dev",
"dev:zitare:web": "pnpm --filter @zitare/web dev",
"dev:zitare:landing": "pnpm --filter @zitare/landing dev",
"dev:zitare:backend": "pnpm --filter @zitare/backend dev",
"dev:zitare:app": "turbo run dev --filter=@zitare/web --filter=@zitare/backend",
+ "dev:zitare:full": "./scripts/setup-databases.sh zitare && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:zitare:backend\" \"pnpm dev:zitare:web\"",
"contacts:dev": "turbo run dev --filter=contacts...",
"dev:contacts:mobile": "pnpm --filter @contacts/mobile dev",
"dev:contacts:web": "pnpm --filter @contacts/web dev",
"dev:contacts:landing": "pnpm --filter @contacts/landing dev",
"dev:contacts:backend": "pnpm --filter @contacts/backend dev",
"dev:contacts:app": "turbo run dev --filter=@contacts/web --filter=@contacts/backend",
+ "dev:contacts:full": "./scripts/setup-databases.sh contacts && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:contacts:backend\" \"pnpm dev:contacts:web\"",
"contacts:db:push": "pnpm --filter @contacts/backend db:push",
"contacts:db:studio": "pnpm --filter @contacts/backend db:studio",
"contacts:db:seed": "pnpm --filter @contacts/backend db:seed",
@@ -67,6 +73,7 @@
"dev:calendar:landing": "pnpm --filter @calendar/landing dev",
"dev:calendar:backend": "pnpm --filter @calendar/backend dev",
"dev:calendar:app": "turbo run dev --filter=@calendar/web --filter=@calendar/backend",
+ "dev:calendar:full": "./scripts/setup-databases.sh calendar && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:calendar:backend\" \"pnpm dev:calendar:web\"",
"calendar:db:push": "pnpm --filter @calendar/backend db:push",
"calendar:db:studio": "pnpm --filter @calendar/backend db:studio",
"calendar:db:seed": "pnpm --filter @calendar/backend db:seed",
@@ -75,6 +82,7 @@
"dev:clock:landing": "pnpm --filter @clock/landing dev",
"dev:clock:backend": "pnpm --filter @clock/backend dev",
"dev:clock:app": "turbo run dev --filter=@clock/web --filter=@clock/backend",
+ "dev:clock:full": "./scripts/setup-databases.sh clock && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:clock:backend\" \"pnpm dev:clock:web\"",
"clock:db:push": "pnpm --filter @clock/backend db:push",
"clock:db:studio": "pnpm --filter @clock/backend db:studio",
"clock:db:seed": "pnpm --filter @clock/backend db:seed",
@@ -92,6 +100,7 @@
"dev:todo:landing": "pnpm --filter @todo/landing dev",
"dev:todo:backend": "pnpm --filter @todo/backend dev",
"dev:todo:app": "turbo run dev --filter=@todo/web --filter=@todo/backend",
+ "dev:todo:full": "./scripts/setup-databases.sh todo && ./scripts/setup-databases.sh auth && concurrently -n auth,backend,web -c blue,green,cyan \"pnpm dev:auth\" \"pnpm dev:todo:backend\" \"pnpm dev:todo:web\"",
"todo:db:push": "pnpm --filter @todo/backend db:push",
"todo:db:studio": "pnpm --filter @todo/backend db:studio",
"todo:db:seed": "pnpm --filter @todo/backend db:seed",
diff --git a/scripts/setup-databases.sh b/scripts/setup-databases.sh
new file mode 100755
index 000000000..9fbb75809
--- /dev/null
+++ b/scripts/setup-databases.sh
@@ -0,0 +1,169 @@
+#!/bin/bash
+
+# Setup script for creating databases and pushing schemas
+# Usage: ./scripts/setup-databases.sh [service]
+# Examples:
+# ./scripts/setup-databases.sh # Setup all
+# ./scripts/setup-databases.sh chat # Setup only chat
+# ./scripts/setup-databases.sh auth # Setup only auth
+
+set -e
+
+# Database connection details (from .env.development)
+DB_HOST="${DB_HOST:-localhost}"
+DB_PORT="${DB_PORT:-5432}"
+DB_USER="${POSTGRES_USER:-manacore}"
+DB_PASSWORD="${POSTGRES_PASSWORD:-devpassword}"
+
+# Colors for output
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+
+echo -e "${GREEN}🗄️ Database Setup Script${NC}"
+echo "======================================"
+
+# Function to create database if it doesn't exist
+create_db_if_not_exists() {
+ local db_name=$1
+ echo -e "${YELLOW}Checking database: ${db_name}${NC}"
+
+ if PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -tc \
+ "SELECT 1 FROM pg_database WHERE datname = '$db_name'" | grep -q 1; then
+ echo -e " ${GREEN}✓ Exists${NC}"
+ else
+ echo -e " Creating database ${db_name}..."
+ PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -c "CREATE DATABASE $db_name;" > /dev/null
+ PGPASSWORD=$DB_PASSWORD psql -h $DB_HOST -p $DB_PORT -U $DB_USER -d postgres -c "GRANT ALL PRIVILEGES ON DATABASE $db_name TO $DB_USER;" > /dev/null
+ echo -e " ${GREEN}✓ Created${NC}"
+ fi
+}
+
+# Function to push schema for a service
+push_schema() {
+ local filter=$1
+ local name=$2
+ echo -e "${YELLOW}Pushing schema for ${name}...${NC}"
+ # Use --force to auto-approve in development (skips interactive prompts)
+ if pnpm --filter "$filter" db:push --force 2>/dev/null; then
+ echo -e " ${GREEN}✓ Schema pushed${NC}"
+ else
+ echo -e " ${RED}✗ Failed (may not have db:push script)${NC}"
+ fi
+}
+
+# All databases that should exist
+ALL_DATABASES=(
+ "manacore"
+ "chat"
+ "zitare"
+ "contacts"
+ "calendar"
+ "clock"
+ "todo"
+ "manadeck"
+ "storage"
+ "mail"
+ "moodlit"
+ "finance"
+ "inventory"
+ "techbase"
+ "voxel_lava"
+ "figgos"
+)
+
+# Check if specific service requested
+SERVICE_FILTER=${1:-""}
+
+setup_service() {
+ local service=$1
+
+ case $service in
+ auth|mana-core-auth)
+ create_db_if_not_exists "manacore"
+ push_schema "mana-core-auth" "mana-core-auth"
+ ;;
+ chat)
+ create_db_if_not_exists "chat"
+ push_schema "@chat/backend" "chat"
+ ;;
+ zitare)
+ create_db_if_not_exists "zitare"
+ push_schema "@zitare/backend" "zitare"
+ ;;
+ contacts)
+ create_db_if_not_exists "contacts"
+ push_schema "@contacts/backend" "contacts"
+ ;;
+ calendar)
+ create_db_if_not_exists "calendar"
+ push_schema "@calendar/backend" "calendar"
+ ;;
+ clock)
+ create_db_if_not_exists "clock"
+ push_schema "@clock/backend" "clock"
+ ;;
+ todo)
+ create_db_if_not_exists "todo"
+ push_schema "@todo/backend" "todo"
+ ;;
+ manadeck)
+ create_db_if_not_exists "manadeck"
+ push_schema "@manadeck/backend" "manadeck"
+ ;;
+ mail)
+ create_db_if_not_exists "mail"
+ push_schema "@mail/backend" "mail"
+ ;;
+ moodlit)
+ create_db_if_not_exists "moodlit"
+ push_schema "@moodlit/backend" "moodlit"
+ ;;
+ picture)
+ create_db_if_not_exists "picture"
+ push_schema "@picture/backend" "picture"
+ ;;
+ finance)
+ create_db_if_not_exists "finance"
+ push_schema "@finance/backend" "finance"
+ ;;
+ voxel-lava)
+ create_db_if_not_exists "voxel_lava"
+ push_schema "@voxel-lava/backend" "voxel-lava"
+ ;;
+ figgos)
+ create_db_if_not_exists "figgos"
+ push_schema "@figgos/backend" "figgos"
+ ;;
+ *)
+ echo -e "${RED}Unknown service: $service${NC}"
+ echo "Available services: auth, chat, zitare, contacts, calendar, clock, todo, manadeck, mail, moodlit, finance, voxel-lava, figgos"
+ exit 1
+ ;;
+ esac
+}
+
+if [ -n "$SERVICE_FILTER" ]; then
+ echo -e "Setting up for service: ${SERVICE_FILTER}"
+ setup_service "$SERVICE_FILTER"
+ echo -e "\n${GREEN}✓ Setup complete!${NC}"
+ exit 0
+fi
+
+# Setup all databases
+echo -e "\n${GREEN}Step 1: Creating databases${NC}"
+echo "--------------------------------------"
+for db in "${ALL_DATABASES[@]}"; do
+ create_db_if_not_exists "$db"
+done
+
+echo -e "\n${GREEN}Step 2: Pushing schemas${NC}"
+echo "--------------------------------------"
+
+# Push schemas for all known services
+for service in auth chat zitare contacts calendar clock todo manadeck picture mail moodlit finance voxel-lava figgos; do
+ setup_service "$service" 2>/dev/null || true
+done
+
+echo -e "\n${GREEN}✓ Database setup complete!${NC}"
From 541e227c68ebbd24e5ecc84075fef1cd41000ffd Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 14:38:41 +0100
Subject: [PATCH 04/34] =?UTF-8?q?=F0=9F=99=88=20chore:=20ignore=20claude-f?=
=?UTF-8?q?low=20metrics=20from=20git=20tracking?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Remove auto-generated system-metrics.json from version control
---
.claude-flow/metrics/system-metrics.json | 662 -----------------------
.gitignore | 1 +
2 files changed, 1 insertion(+), 662 deletions(-)
delete mode 100644 .claude-flow/metrics/system-metrics.json
diff --git a/.claude-flow/metrics/system-metrics.json b/.claude-flow/metrics/system-metrics.json
deleted file mode 100644
index 8c475465d..000000000
--- a/.claude-flow/metrics/system-metrics.json
+++ /dev/null
@@ -1,662 +0,0 @@
-[
- {
- "timestamp": 1764952212026,
- "memoryTotal": 34359738368,
- "memoryUsed": 34261319680,
- "memoryFree": 98418688,
- "memoryUsagePercent": 99.71356391906738,
- "memoryEfficiency": 0.2864360809326172,
- "cpuCount": 12,
- "cpuLoad": 0.18159993489583334,
- "platform": "darwin",
- "uptime": 316234
- },
- {
- "timestamp": 1764952242025,
- "memoryTotal": 34359738368,
- "memoryUsed": 33978384384,
- "memoryFree": 381353984,
- "memoryUsagePercent": 98.8901138305664,
- "memoryEfficiency": 1.1098861694335938,
- "cpuCount": 12,
- "cpuLoad": 0.3077799479166667,
- "platform": "darwin",
- "uptime": 316264
- },
- {
- "timestamp": 1764952272027,
- "memoryTotal": 34359738368,
- "memoryUsed": 31443189760,
- "memoryFree": 2916548608,
- "memoryUsagePercent": 91.51172637939453,
- "memoryEfficiency": 8.488273620605469,
- "cpuCount": 12,
- "cpuLoad": 0.3214518229166667,
- "platform": "darwin",
- "uptime": 316294
- },
- {
- "timestamp": 1764952302028,
- "memoryTotal": 34359738368,
- "memoryUsed": 32673497088,
- "memoryFree": 1686241280,
- "memoryUsagePercent": 95.09239196777344,
- "memoryEfficiency": 4.9076080322265625,
- "cpuCount": 12,
- "cpuLoad": 0.2928873697916667,
- "platform": "darwin",
- "uptime": 316324
- },
- {
- "timestamp": 1764952332029,
- "memoryTotal": 34359738368,
- "memoryUsed": 33272102912,
- "memoryFree": 1087635456,
- "memoryUsagePercent": 96.83456420898438,
- "memoryEfficiency": 3.165435791015625,
- "cpuCount": 12,
- "cpuLoad": 0.2457275390625,
- "platform": "darwin",
- "uptime": 316354
- },
- {
- "timestamp": 1764952362030,
- "memoryTotal": 34359738368,
- "memoryUsed": 33491288064,
- "memoryFree": 868450304,
- "memoryUsagePercent": 97.47247695922852,
- "memoryEfficiency": 2.5275230407714844,
- "cpuCount": 12,
- "cpuLoad": 0.21903483072916666,
- "platform": "darwin",
- "uptime": 316384
- },
- {
- "timestamp": 1764952392031,
- "memoryTotal": 34359738368,
- "memoryUsed": 33697579008,
- "memoryFree": 662159360,
- "memoryUsagePercent": 98.07286262512207,
- "memoryEfficiency": 1.9271373748779297,
- "cpuCount": 12,
- "cpuLoad": 0.19921875,
- "platform": "darwin",
- "uptime": 316414
- },
- {
- "timestamp": 1764952422033,
- "memoryTotal": 34359738368,
- "memoryUsed": 33743257600,
- "memoryFree": 616480768,
- "memoryUsagePercent": 98.2058048248291,
- "memoryEfficiency": 1.7941951751708984,
- "cpuCount": 12,
- "cpuLoad": 0.3559977213541667,
- "platform": "darwin",
- "uptime": 316444
- },
- {
- "timestamp": 1764952454157,
- "memoryTotal": 34359738368,
- "memoryUsed": 34170847232,
- "memoryFree": 188891136,
- "memoryUsagePercent": 99.45025444030762,
- "memoryEfficiency": 0.5497455596923828,
- "cpuCount": 12,
- "cpuLoad": 0.8998616536458334,
- "platform": "darwin",
- "uptime": 316476
- },
- {
- "timestamp": 1764952484155,
- "memoryTotal": 34359738368,
- "memoryUsed": 34279800832,
- "memoryFree": 79937536,
- "memoryUsagePercent": 99.7673511505127,
- "memoryEfficiency": 0.2326488494873047,
- "cpuCount": 12,
- "cpuLoad": 0.6012369791666666,
- "platform": "darwin",
- "uptime": 316506
- },
- {
- "timestamp": 1764952516186,
- "memoryTotal": 34359738368,
- "memoryUsed": 34296479744,
- "memoryFree": 63258624,
- "memoryUsagePercent": 99.81589317321777,
- "memoryEfficiency": 0.18410682678222656,
- "cpuCount": 12,
- "cpuLoad": 0.4691975911458333,
- "platform": "darwin",
- "uptime": 316538
- },
- {
- "timestamp": 1764952548258,
- "memoryTotal": 34359738368,
- "memoryUsed": 34266447872,
- "memoryFree": 93290496,
- "memoryUsagePercent": 99.72848892211914,
- "memoryEfficiency": 0.2715110778808594,
- "cpuCount": 12,
- "cpuLoad": 0.340087890625,
- "platform": "darwin",
- "uptime": 316570
- },
- {
- "timestamp": 1764952578260,
- "memoryTotal": 34359738368,
- "memoryUsed": 34260549632,
- "memoryFree": 99188736,
- "memoryUsagePercent": 99.71132278442383,
- "memoryEfficiency": 0.2886772155761719,
- "cpuCount": 12,
- "cpuLoad": 0.2496337890625,
- "platform": "darwin",
- "uptime": 316600
- },
- {
- "timestamp": 1764952609578,
- "memoryTotal": 34359738368,
- "memoryUsed": 33692418048,
- "memoryFree": 667320320,
- "memoryUsagePercent": 98.05784225463867,
- "memoryEfficiency": 1.9421577453613281,
- "cpuCount": 12,
- "cpuLoad": 0.19681803385416666,
- "platform": "darwin",
- "uptime": 316631
- },
- {
- "timestamp": 1764952641004,
- "memoryTotal": 34359738368,
- "memoryUsed": 34198962176,
- "memoryFree": 160776192,
- "memoryUsagePercent": 99.53207969665527,
- "memoryEfficiency": 0.46792030334472656,
- "cpuCount": 12,
- "cpuLoad": 0.23486328125,
- "platform": "darwin",
- "uptime": 316663
- },
- {
- "timestamp": 1764952671007,
- "memoryTotal": 34359738368,
- "memoryUsed": 34222686208,
- "memoryFree": 137052160,
- "memoryUsagePercent": 99.60112571716309,
- "memoryEfficiency": 0.39887428283691406,
- "cpuCount": 12,
- "cpuLoad": 0.18770345052083334,
- "platform": "darwin",
- "uptime": 316693
- },
- {
- "timestamp": 1764952702795,
- "memoryTotal": 34359738368,
- "memoryUsed": 34269872128,
- "memoryFree": 89866240,
- "memoryUsagePercent": 99.73845481872559,
- "memoryEfficiency": 0.26154518127441406,
- "cpuCount": 12,
- "cpuLoad": 0.15791829427083334,
- "platform": "darwin",
- "uptime": 316724
- },
- {
- "timestamp": 1764952732797,
- "memoryTotal": 34359738368,
- "memoryUsed": 34242723840,
- "memoryFree": 117014528,
- "memoryUsagePercent": 99.65944290161133,
- "memoryEfficiency": 0.3405570983886719,
- "cpuCount": 12,
- "cpuLoad": 0.12320963541666667,
- "platform": "darwin",
- "uptime": 316754
- },
- {
- "timestamp": 1764952764879,
- "memoryTotal": 34359738368,
- "memoryUsed": 34213707776,
- "memoryFree": 146030592,
- "memoryUsagePercent": 99.57499504089355,
- "memoryEfficiency": 0.4250049591064453,
- "cpuCount": 12,
- "cpuLoad": 0.11909993489583333,
- "platform": "darwin",
- "uptime": 316786
- },
- {
- "timestamp": 1764952796332,
- "memoryTotal": 34359738368,
- "memoryUsed": 34039250944,
- "memoryFree": 320487424,
- "memoryUsagePercent": 99.06725883483887,
- "memoryEfficiency": 0.9327411651611328,
- "cpuCount": 12,
- "cpuLoad": 0.135009765625,
- "platform": "darwin",
- "uptime": 316818
- },
- {
- "timestamp": 1764952826334,
- "memoryTotal": 34359738368,
- "memoryUsed": 34186772480,
- "memoryFree": 172965888,
- "memoryUsagePercent": 99.49660301208496,
- "memoryEfficiency": 0.5033969879150391,
- "cpuCount": 12,
- "cpuLoad": 0.21162923177083334,
- "platform": "darwin",
- "uptime": 316848
- },
- {
- "timestamp": 1764952858356,
- "memoryTotal": 34359738368,
- "memoryUsed": 34186379264,
- "memoryFree": 173359104,
- "memoryUsagePercent": 99.49545860290527,
- "memoryEfficiency": 0.5045413970947266,
- "cpuCount": 12,
- "cpuLoad": 0.2408447265625,
- "platform": "darwin",
- "uptime": 316880
- },
- {
- "timestamp": 1764952889730,
- "memoryTotal": 34359738368,
- "memoryUsed": 34280357888,
- "memoryFree": 79380480,
- "memoryUsagePercent": 99.76897239685059,
- "memoryEfficiency": 0.23102760314941406,
- "cpuCount": 12,
- "cpuLoad": 0.3212890625,
- "platform": "darwin",
- "uptime": 316911
- },
- {
- "timestamp": 1764952919730,
- "memoryTotal": 34359738368,
- "memoryUsed": 33718976512,
- "memoryFree": 640761856,
- "memoryUsagePercent": 98.1351375579834,
- "memoryEfficiency": 1.8648624420166016,
- "cpuCount": 12,
- "cpuLoad": 0.2577311197916667,
- "platform": "darwin",
- "uptime": 316941
- },
- {
- "timestamp": 1764952949731,
- "memoryTotal": 34359738368,
- "memoryUsed": 33727643648,
- "memoryFree": 632094720,
- "memoryUsagePercent": 98.16036224365234,
- "memoryEfficiency": 1.8396377563476562,
- "cpuCount": 12,
- "cpuLoad": 0.2508138020833333,
- "platform": "darwin",
- "uptime": 316971
- },
- {
- "timestamp": 1764952979732,
- "memoryTotal": 34359738368,
- "memoryUsed": 33519927296,
- "memoryFree": 839811072,
- "memoryUsagePercent": 97.55582809448242,
- "memoryEfficiency": 2.444171905517578,
- "cpuCount": 12,
- "cpuLoad": 1.6138916015625,
- "platform": "darwin",
- "uptime": 317001
- },
- {
- "timestamp": 1764953009732,
- "memoryTotal": 34359738368,
- "memoryUsed": 33729118208,
- "memoryFree": 630620160,
- "memoryUsagePercent": 98.16465377807617,
- "memoryEfficiency": 1.8353462219238281,
- "cpuCount": 12,
- "cpuLoad": 1.1908365885416667,
- "platform": "darwin",
- "uptime": 317031
- },
- {
- "timestamp": 1764953039733,
- "memoryTotal": 34359738368,
- "memoryUsed": 34194341888,
- "memoryFree": 165396480,
- "memoryUsagePercent": 99.51863288879395,
- "memoryEfficiency": 0.4813671112060547,
- "cpuCount": 12,
- "cpuLoad": 0.8716227213541666,
- "platform": "darwin",
- "uptime": 317061
- },
- {
- "timestamp": 1764953069734,
- "memoryTotal": 34359738368,
- "memoryUsed": 34195587072,
- "memoryFree": 164151296,
- "memoryUsagePercent": 99.52225685119629,
- "memoryEfficiency": 0.47774314880371094,
- "cpuCount": 12,
- "cpuLoad": 0.6322835286458334,
- "platform": "darwin",
- "uptime": 317091
- },
- {
- "timestamp": 1764953099734,
- "memoryTotal": 34359738368,
- "memoryUsed": 34240970752,
- "memoryFree": 118767616,
- "memoryUsagePercent": 99.65434074401855,
- "memoryEfficiency": 0.3456592559814453,
- "cpuCount": 12,
- "cpuLoad": 0.5132242838541666,
- "platform": "darwin",
- "uptime": 317121
- },
- {
- "timestamp": 1764953129734,
- "memoryTotal": 34359738368,
- "memoryUsed": 34246590464,
- "memoryFree": 113147904,
- "memoryUsagePercent": 99.67069625854492,
- "memoryEfficiency": 0.3293037414550781,
- "cpuCount": 12,
- "cpuLoad": 0.4134521484375,
- "platform": "darwin",
- "uptime": 317151
- },
- {
- "timestamp": 1764953159735,
- "memoryTotal": 34359738368,
- "memoryUsed": 34252914688,
- "memoryFree": 106823680,
- "memoryUsagePercent": 99.68910217285156,
- "memoryEfficiency": 0.3108978271484375,
- "cpuCount": 12,
- "cpuLoad": 0.5005696614583334,
- "platform": "darwin",
- "uptime": 317181
- },
- {
- "timestamp": 1764953189735,
- "memoryTotal": 34359738368,
- "memoryUsed": 34234728448,
- "memoryFree": 125009920,
- "memoryUsagePercent": 99.63617324829102,
- "memoryEfficiency": 0.3638267517089844,
- "cpuCount": 12,
- "cpuLoad": 0.3972981770833333,
- "platform": "darwin",
- "uptime": 317211
- },
- {
- "timestamp": 1764953219736,
- "memoryTotal": 34359738368,
- "memoryUsed": 33779810304,
- "memoryFree": 579928064,
- "memoryUsagePercent": 98.31218719482422,
- "memoryEfficiency": 1.6878128051757812,
- "cpuCount": 12,
- "cpuLoad": 0.3188883463541667,
- "platform": "darwin",
- "uptime": 317241
- },
- {
- "timestamp": 1764953249736,
- "memoryTotal": 34359738368,
- "memoryUsed": 34159230976,
- "memoryFree": 200507392,
- "memoryUsagePercent": 99.41644668579102,
- "memoryEfficiency": 0.5835533142089844,
- "cpuCount": 12,
- "cpuLoad": 0.2990315755208333,
- "platform": "darwin",
- "uptime": 317271
- },
- {
- "timestamp": 1764953279736,
- "memoryTotal": 34359738368,
- "memoryUsed": 34207203328,
- "memoryFree": 152535040,
- "memoryUsagePercent": 99.55606460571289,
- "memoryEfficiency": 0.4439353942871094,
- "cpuCount": 12,
- "cpuLoad": 0.4497477213541667,
- "platform": "darwin",
- "uptime": 317301
- },
- {
- "timestamp": 1764953309737,
- "memoryTotal": 34359738368,
- "memoryUsed": 34149695488,
- "memoryFree": 210042880,
- "memoryUsagePercent": 99.3886947631836,
- "memoryEfficiency": 0.6113052368164062,
- "cpuCount": 12,
- "cpuLoad": 0.3463541666666667,
- "platform": "darwin",
- "uptime": 317331
- },
- {
- "timestamp": 1764953339738,
- "memoryTotal": 34359738368,
- "memoryUsed": 33949761536,
- "memoryFree": 409976832,
- "memoryUsagePercent": 98.80681037902832,
- "memoryEfficiency": 1.1931896209716797,
- "cpuCount": 12,
- "cpuLoad": 0.3301188151041667,
- "platform": "darwin",
- "uptime": 317361
- },
- {
- "timestamp": 1764953369738,
- "memoryTotal": 34359738368,
- "memoryUsed": 34282471424,
- "memoryFree": 77266944,
- "memoryUsagePercent": 99.7751235961914,
- "memoryEfficiency": 0.22487640380859375,
- "cpuCount": 12,
- "cpuLoad": 0.28466796875,
- "platform": "darwin",
- "uptime": 317391
- },
- {
- "timestamp": 1764953399740,
- "memoryTotal": 34359738368,
- "memoryUsed": 32868974592,
- "memoryFree": 1490763776,
- "memoryUsagePercent": 95.66130638122559,
- "memoryEfficiency": 4.338693618774414,
- "cpuCount": 12,
- "cpuLoad": 0.224365234375,
- "platform": "darwin",
- "uptime": 317421
- },
- {
- "timestamp": 1764953429741,
- "memoryTotal": 34359738368,
- "memoryUsed": 33954299904,
- "memoryFree": 405438464,
- "memoryUsagePercent": 98.82001876831055,
- "memoryEfficiency": 1.1799812316894531,
- "cpuCount": 12,
- "cpuLoad": 0.18465169270833334,
- "platform": "darwin",
- "uptime": 317451
- },
- {
- "timestamp": 1764953459742,
- "memoryTotal": 34359738368,
- "memoryUsed": 33574420480,
- "memoryFree": 785317888,
- "memoryUsagePercent": 97.71442413330078,
- "memoryEfficiency": 2.2855758666992188,
- "cpuCount": 12,
- "cpuLoad": 0.1524658203125,
- "platform": "darwin",
- "uptime": 317481
- },
- {
- "timestamp": 1764953489742,
- "memoryTotal": 34359738368,
- "memoryUsed": 33800306688,
- "memoryFree": 559431680,
- "memoryUsagePercent": 98.37183952331543,
- "memoryEfficiency": 1.6281604766845703,
- "cpuCount": 12,
- "cpuLoad": 0.20756022135416666,
- "platform": "darwin",
- "uptime": 317511
- },
- {
- "timestamp": 1764953519743,
- "memoryTotal": 34359738368,
- "memoryUsed": 34025553920,
- "memoryFree": 334184448,
- "memoryUsagePercent": 99.02739524841309,
- "memoryEfficiency": 0.9726047515869141,
- "cpuCount": 12,
- "cpuLoad": 0.23970540364583334,
- "platform": "darwin",
- "uptime": 317541
- },
- {
- "timestamp": 1764953549744,
- "memoryTotal": 34359738368,
- "memoryUsed": 34073149440,
- "memoryFree": 286588928,
- "memoryUsagePercent": 99.1659164428711,
- "memoryEfficiency": 0.8340835571289062,
- "cpuCount": 12,
- "cpuLoad": 0.2720133463541667,
- "platform": "darwin",
- "uptime": 317571
- },
- {
- "timestamp": 1764953579745,
- "memoryTotal": 34359738368,
- "memoryUsed": 34250440704,
- "memoryFree": 109297664,
- "memoryUsagePercent": 99.6819019317627,
- "memoryEfficiency": 0.3180980682373047,
- "cpuCount": 12,
- "cpuLoad": 0.2032470703125,
- "platform": "darwin",
- "uptime": 317601
- },
- {
- "timestamp": 1764953609745,
- "memoryTotal": 34359738368,
- "memoryUsed": 34292432896,
- "memoryFree": 67305472,
- "memoryUsagePercent": 99.80411529541016,
- "memoryEfficiency": 0.19588470458984375,
- "cpuCount": 12,
- "cpuLoad": 0.22269694010416666,
- "platform": "darwin",
- "uptime": 317631
- },
- {
- "timestamp": 1764953639747,
- "memoryTotal": 34359738368,
- "memoryUsed": 34245033984,
- "memoryFree": 114704384,
- "memoryUsagePercent": 99.66616630554199,
- "memoryEfficiency": 0.3338336944580078,
- "cpuCount": 12,
- "cpuLoad": 0.1802978515625,
- "platform": "darwin",
- "uptime": 317661
- },
- {
- "timestamp": 1764953669748,
- "memoryTotal": 34359738368,
- "memoryUsed": 34262171648,
- "memoryFree": 97566720,
- "memoryUsagePercent": 99.71604347229004,
- "memoryEfficiency": 0.28395652770996094,
- "cpuCount": 12,
- "cpuLoad": 0.16731770833333334,
- "platform": "darwin",
- "uptime": 317691
- },
- {
- "timestamp": 1764953699748,
- "memoryTotal": 34359738368,
- "memoryUsed": 33642708992,
- "memoryFree": 717029376,
- "memoryUsagePercent": 97.91316986083984,
- "memoryEfficiency": 2.0868301391601562,
- "cpuCount": 12,
- "cpuLoad": 0.1925048828125,
- "platform": "darwin",
- "uptime": 317721
- },
- {
- "timestamp": 1764953729749,
- "memoryTotal": 34359738368,
- "memoryUsed": 33480491008,
- "memoryFree": 879247360,
- "memoryUsagePercent": 97.44105339050293,
- "memoryEfficiency": 2.5589466094970703,
- "cpuCount": 12,
- "cpuLoad": 0.327880859375,
- "platform": "darwin",
- "uptime": 317751
- },
- {
- "timestamp": 1764953759749,
- "memoryTotal": 34359738368,
- "memoryUsed": 34072002560,
- "memoryFree": 287735808,
- "memoryUsagePercent": 99.16257858276367,
- "memoryEfficiency": 0.8374214172363281,
- "cpuCount": 12,
- "cpuLoad": 0.2535400390625,
- "platform": "darwin",
- "uptime": 317781
- },
- {
- "timestamp": 1764953789750,
- "memoryTotal": 34359738368,
- "memoryUsed": 34169667584,
- "memoryFree": 190070784,
- "memoryUsagePercent": 99.44682121276855,
- "memoryEfficiency": 0.5531787872314453,
- "cpuCount": 12,
- "cpuLoad": 0.19832356770833334,
- "platform": "darwin",
- "uptime": 317811
- },
- {
- "timestamp": 1764953819750,
- "memoryTotal": 34359738368,
- "memoryUsed": 34144272384,
- "memoryFree": 215465984,
- "memoryUsagePercent": 99.37291145324707,
- "memoryEfficiency": 0.6270885467529297,
- "cpuCount": 12,
- "cpuLoad": 0.209716796875,
- "platform": "darwin",
- "uptime": 317841
- },
- {
- "timestamp": 1764953849751,
- "memoryTotal": 34359738368,
- "memoryUsed": 34237202432,
- "memoryFree": 122535936,
- "memoryUsagePercent": 99.64337348937988,
- "memoryEfficiency": 0.3566265106201172,
- "cpuCount": 12,
- "cpuLoad": 0.24357096354166666,
- "platform": "darwin",
- "uptime": 317871
- }
-]
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 1a98da2cc..5303d2a89 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,3 +95,4 @@ yarn.lock
# Claude Flow metrics
.claude-flow/
+.claude-flow/metrics/
From 60756f7105d908989d7b31a0a02e5e2cbca76ba7 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 14:44:17 +0100
Subject: [PATCH 05/34] small linting fixes
---
.../mobile/components/documents/BatchDocumentCreator.tsx | 6 +++---
.../apps/mobile/components/navigation/Breadcrumbs.tsx | 4 +---
apps/context/apps/mobile/components/ui/FilterPill.tsx | 2 +-
3 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/apps/context/apps/mobile/components/documents/BatchDocumentCreator.tsx b/apps/context/apps/mobile/components/documents/BatchDocumentCreator.tsx
index 767a762ac..701e23324 100644
--- a/apps/context/apps/mobile/components/documents/BatchDocumentCreator.tsx
+++ b/apps/context/apps/mobile/components/documents/BatchDocumentCreator.tsx
@@ -50,9 +50,9 @@ export const BatchDocumentCreator: React.FC = ({
const [subjectList, setSubjectList] = useState([]);
const [documents, setDocuments] = useState([]);
const [selectedDocuments, setSelectedDocuments] = useState([]);
- const [documentFilter, setDocumentFilter] = useState<
- 'all' | 'text' | 'context' | 'prompt'
- >('context');
+ const [documentFilter, setDocumentFilter] = useState<'all' | 'text' | 'context' | 'prompt'>(
+ 'context'
+ );
const [promptDocuments, setPromptDocuments] = useState([]);
const { mode, colors } = useTheme();
const isDark = mode === 'dark';
diff --git a/apps/context/apps/mobile/components/navigation/Breadcrumbs.tsx b/apps/context/apps/mobile/components/navigation/Breadcrumbs.tsx
index bbc3537ce..676242dc2 100644
--- a/apps/context/apps/mobile/components/navigation/Breadcrumbs.tsx
+++ b/apps/context/apps/mobile/components/navigation/Breadcrumbs.tsx
@@ -216,9 +216,7 @@ export const Breadcrumbs: React.FC = ({
? 'font-medium text-gray-800 dark:text-gray-200'
: 'text-gray-500 dark:text-gray-400'
)}
- style={[
- pressed && !isLast && styles.textHovered,
- ]}
+ style={[pressed && !isLast && styles.textHovered]}
>
{item.label}
diff --git a/apps/context/apps/mobile/components/ui/FilterPill.tsx b/apps/context/apps/mobile/components/ui/FilterPill.tsx
index e61c519d6..e9ff5c3dc 100644
--- a/apps/context/apps/mobile/components/ui/FilterPill.tsx
+++ b/apps/context/apps/mobile/components/ui/FilterPill.tsx
@@ -186,7 +186,7 @@ export const FilterPill: React.FC = ({
: isDark
? '#1f2937'
: '#d1d5db',
- opacity: disabled ? 0.6 : (pressed ? 0.8 : 1),
+ opacity: disabled ? 0.6 : pressed ? 0.8 : 1,
},
]}
onPress={disabled ? undefined : actionButton.onPress}
From bb4e12c36ec3c878e2d5954b1929670e9a833e5f Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 15:51:57 +0100
Subject: [PATCH 06/34] =?UTF-8?q?=F0=9F=90=9B=20fix:=20resolve=20auth=20is?=
=?UTF-8?q?sues=20in=20Manacore,=20Calendar,=20and=20Clock=20apps?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Manacore: fix import path for authStore (authStore.svelte → auth.svelte)
- Calendar: simplify root layout by moving PillNavigation to (app) layout
- Clock: update login page to use correct shared-auth-ui props (onSignIn, logo, goto)
- Add ClockLogo component to shared-branding package
---
.../apps/web/src/routes/+layout.svelte | 49 +------------------
.../web/src/routes/(auth)/login/+page.svelte | 38 +++++++-------
.../src/routes/(app)/settings/+page.svelte | 2 +-
packages/shared-branding/src/index.ts | 1 +
.../src/logos/ClockLogo.svelte | 13 +++++
packages/shared-branding/src/logos/index.ts | 1 +
6 files changed, 35 insertions(+), 69 deletions(-)
create mode 100644 packages/shared-branding/src/logos/ClockLogo.svelte
diff --git a/apps/calendar/apps/web/src/routes/+layout.svelte b/apps/calendar/apps/web/src/routes/+layout.svelte
index 6d8411e8f..6cbb7c5d9 100644
--- a/apps/calendar/apps/web/src/routes/+layout.svelte
+++ b/apps/calendar/apps/web/src/routes/+layout.svelte
@@ -32,52 +32,7 @@
{:else}
-
-
-
-
-
- {@render children()}
-
-
+
+ {@render children()}
{/if}
diff --git a/apps/clock/apps/web/src/routes/(auth)/login/+page.svelte b/apps/clock/apps/web/src/routes/(auth)/login/+page.svelte
index 982d694bf..41dd500ee 100644
--- a/apps/clock/apps/web/src/routes/(auth)/login/+page.svelte
+++ b/apps/clock/apps/web/src/routes/(auth)/login/+page.svelte
@@ -1,34 +1,30 @@
diff --git a/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte b/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte
index 56ff38f9a..fe223d193 100644
--- a/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte
+++ b/apps/manacore/apps/web/src/routes/(app)/settings/+page.svelte
@@ -1,7 +1,7 @@
+
+
diff --git a/packages/shared-branding/src/logos/index.ts b/packages/shared-branding/src/logos/index.ts
index 06ce0a38d..235e0e2c6 100644
--- a/packages/shared-branding/src/logos/index.ts
+++ b/packages/shared-branding/src/logos/index.ts
@@ -18,3 +18,4 @@ export { default as TodoLogo } from './TodoLogo.svelte';
export { default as MailLogo } from './MailLogo.svelte';
export { default as MoodlitLogo } from './MoodlitLogo.svelte';
export { default as InventoryLogo } from './InventoryLogo.svelte';
+export { default as ClockLogo } from './ClockLogo.svelte';
From 5e0b5a8e7ab685b11dd007b47667fec54fc9e718 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 16:04:50 +0100
Subject: [PATCH 07/34] =?UTF-8?q?=F0=9F=9A=80=20ci:=20add=20Docker=20deplo?=
=?UTF-8?q?yment=20for=20Manacore,=20Todo,=20Calendar,=20and=20Clock=20app?=
=?UTF-8?q?s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add complete Docker deployment infrastructure for 4 new applications:
- Dockerfiles for backend (NestJS) and web (SvelteKit) apps
- docker-entrypoint.sh scripts with PostgreSQL wait and schema push
- Updated docker-compose.staging.yml with 7 new services
- Updated CI/CD workflows with build matrix and health checks
---
.github/workflows/cd-staging.yml | 93 +++++++
.github/workflows/ci.yml | 7 +
apps/calendar/apps/backend/Dockerfile | 68 +++++
.../apps/backend/docker-entrypoint.sh | 23 ++
apps/calendar/apps/web/Dockerfile | 72 ++++++
apps/clock/apps/backend/Dockerfile | 68 +++++
apps/clock/apps/backend/docker-entrypoint.sh | 23 ++
apps/clock/apps/web/Dockerfile | 72 ++++++
apps/manacore/apps/web/Dockerfile | 74 ++++++
apps/todo/apps/backend/Dockerfile | 67 +++++
apps/todo/apps/backend/docker-entrypoint.sh | 23 ++
apps/todo/apps/web/Dockerfile | 72 ++++++
docker-compose.staging.yml | 236 ++++++++++++++++++
13 files changed, 898 insertions(+)
create mode 100644 apps/calendar/apps/backend/Dockerfile
create mode 100644 apps/calendar/apps/backend/docker-entrypoint.sh
create mode 100644 apps/calendar/apps/web/Dockerfile
create mode 100644 apps/clock/apps/backend/Dockerfile
create mode 100644 apps/clock/apps/backend/docker-entrypoint.sh
create mode 100644 apps/clock/apps/web/Dockerfile
create mode 100644 apps/manacore/apps/web/Dockerfile
create mode 100644 apps/todo/apps/backend/Dockerfile
create mode 100644 apps/todo/apps/backend/docker-entrypoint.sh
create mode 100644 apps/todo/apps/web/Dockerfile
diff --git a/.github/workflows/cd-staging.yml b/.github/workflows/cd-staging.yml
index 8c6e83955..1852ed42a 100644
--- a/.github/workflows/cd-staging.yml
+++ b/.github/workflows/cd-staging.yml
@@ -24,6 +24,13 @@ on:
- mana-core-auth
- chat-backend
- chat-web
+ - manacore-web
+ - todo-backend
+ - todo-web
+ - calendar-backend
+ - calendar-web
+ - clock-backend
+ - clock-web
workflow_call:
permissions:
@@ -184,6 +191,15 @@ jobs:
# Create chat database (for chat-backend service)
docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE chat;" 2>/dev/null || echo "chat database already exists"
+ # Create todo database (for todo-backend service)
+ docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE todo;" 2>/dev/null || echo "todo database already exists"
+
+ # Create calendar database (for calendar-backend service)
+ docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE calendar;" 2>/dev/null || echo "calendar database already exists"
+
+ # Create clock database (for clock-backend service)
+ docker compose exec -T postgres psql -U postgres -c "CREATE DATABASE clock;" 2>/dev/null || echo "clock database already exists"
+
echo "✅ Databases ready"
EOF
@@ -238,6 +254,83 @@ jobs:
exit 1
fi
+ # Check manacore-web
+ echo "Checking manacore-web..."
+ if docker compose exec -T manacore-web wget -q -O - http://localhost:5173/health > /dev/null 2>&1; then
+ echo "✅ manacore-web is healthy"
+ else
+ echo "❌ manacore-web health check failed"
+ echo "=== Logs ==="
+ docker compose logs --tail=50 manacore-web
+ exit 1
+ fi
+
+ # Check todo-backend
+ echo "Checking todo-backend..."
+ if docker compose exec -T todo-backend wget -q -O - http://localhost:3018/api/v1/health > /dev/null 2>&1; then
+ echo "✅ todo-backend is healthy"
+ else
+ echo "❌ todo-backend health check failed"
+ echo "=== Logs ==="
+ docker compose logs --tail=50 todo-backend
+ exit 1
+ fi
+
+ # Check todo-web
+ echo "Checking todo-web..."
+ if docker compose exec -T todo-web wget -q -O - http://localhost:5188/health > /dev/null 2>&1; then
+ echo "✅ todo-web is healthy"
+ else
+ echo "❌ todo-web health check failed"
+ echo "=== Logs ==="
+ docker compose logs --tail=50 todo-web
+ exit 1
+ fi
+
+ # Check calendar-backend
+ echo "Checking calendar-backend..."
+ if docker compose exec -T calendar-backend wget -q -O - http://localhost:3016/api/v1/health > /dev/null 2>&1; then
+ echo "✅ calendar-backend is healthy"
+ else
+ echo "❌ calendar-backend health check failed"
+ echo "=== Logs ==="
+ docker compose logs --tail=50 calendar-backend
+ exit 1
+ fi
+
+ # Check calendar-web
+ echo "Checking calendar-web..."
+ if docker compose exec -T calendar-web wget -q -O - http://localhost:5186/health > /dev/null 2>&1; then
+ echo "✅ calendar-web is healthy"
+ else
+ echo "❌ calendar-web health check failed"
+ echo "=== Logs ==="
+ docker compose logs --tail=50 calendar-web
+ exit 1
+ fi
+
+ # Check clock-backend
+ echo "Checking clock-backend..."
+ if docker compose exec -T clock-backend wget -q -O - http://localhost:3017/api/v1/health > /dev/null 2>&1; then
+ echo "✅ clock-backend is healthy"
+ else
+ echo "❌ clock-backend health check failed"
+ echo "=== Logs ==="
+ docker compose logs --tail=50 clock-backend
+ exit 1
+ fi
+
+ # Check clock-web
+ echo "Checking clock-web..."
+ if docker compose exec -T clock-web wget -q -O - http://localhost:5187/health > /dev/null 2>&1; then
+ echo "✅ clock-web is healthy"
+ else
+ echo "❌ clock-web health check failed"
+ echo "=== Logs ==="
+ docker compose logs --tail=50 clock-web
+ exit 1
+ fi
+
echo ""
echo "✅ All health checks passed!"
EOF
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f30b9b10a..1d56ac667 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -86,6 +86,13 @@ jobs:
- { name: 'mana-core-auth', path: 'services/mana-core-auth', port: '3001' }
- { name: 'chat-backend', path: 'apps/chat/apps/backend', port: '3002' }
- { name: 'chat-web', path: 'apps/chat/apps/web', port: '3000' }
+ - { name: 'manacore-web', path: 'apps/manacore/apps/web', port: '5173' }
+ - { name: 'todo-backend', path: 'apps/todo/apps/backend', port: '3018' }
+ - { name: 'todo-web', path: 'apps/todo/apps/web', port: '5188' }
+ - { name: 'calendar-backend', path: 'apps/calendar/apps/backend', port: '3016' }
+ - { name: 'calendar-web', path: 'apps/calendar/apps/web', port: '5186' }
+ - { name: 'clock-backend', path: 'apps/clock/apps/backend', port: '3017' }
+ - { name: 'clock-web', path: 'apps/clock/apps/web', port: '5187' }
fail-fast: false
steps:
- name: Checkout code
diff --git a/apps/calendar/apps/backend/Dockerfile b/apps/calendar/apps/backend/Dockerfile
new file mode 100644
index 000000000..129862166
--- /dev/null
+++ b/apps/calendar/apps/backend/Dockerfile
@@ -0,0 +1,68 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages
+COPY packages/shared-errors ./packages/shared-errors
+COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
+
+# Copy calendar packages and backend
+COPY apps/calendar/packages ./apps/calendar/packages
+COPY apps/calendar/apps/backend ./apps/calendar/apps/backend
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages first
+WORKDIR /app/packages/shared-errors
+RUN pnpm build
+
+WORKDIR /app/packages/shared-nestjs-auth
+RUN pnpm build
+
+# Build the backend
+WORKDIR /app/apps/calendar/apps/backend
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Install pnpm and postgresql-client for health checks
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate \
+ && apk add --no-cache postgresql-client
+
+WORKDIR /app
+
+# Copy everything from builder (including node_modules)
+COPY --from=builder /app/pnpm-workspace.yaml ./
+COPY --from=builder /app/package.json ./
+COPY --from=builder /app/pnpm-lock.yaml ./
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/packages ./packages
+COPY --from=builder /app/apps/calendar ./apps/calendar
+
+# Copy entrypoint script
+COPY apps/calendar/apps/backend/docker-entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+
+WORKDIR /app/apps/calendar/apps/backend
+
+# Expose port
+EXPOSE 3016
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3016/api/v1/health || exit 1
+
+# Run entrypoint script
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["node", "dist/main.js"]
diff --git a/apps/calendar/apps/backend/docker-entrypoint.sh b/apps/calendar/apps/backend/docker-entrypoint.sh
new file mode 100644
index 000000000..bda857019
--- /dev/null
+++ b/apps/calendar/apps/backend/docker-entrypoint.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -e
+
+echo "=== Calendar Backend Entrypoint ==="
+
+# Wait for PostgreSQL to be ready
+echo "Waiting for PostgreSQL..."
+until pg_isready -h ${DB_HOST:-postgres} -p ${DB_PORT:-5432} -U ${DB_USER:-postgres} 2>/dev/null; do
+ echo "PostgreSQL is unavailable - sleeping"
+ sleep 2
+done
+echo "PostgreSQL is up!"
+
+cd /app/apps/calendar/apps/backend
+
+# Run schema push
+echo "Pushing database schema..."
+npx drizzle-kit push --force
+echo "Schema push completed!"
+
+# Execute the main command
+echo "Starting application..."
+exec "$@"
diff --git a/apps/calendar/apps/web/Dockerfile b/apps/calendar/apps/web/Dockerfile
new file mode 100644
index 000000000..04040c048
--- /dev/null
+++ b/apps/calendar/apps/web/Dockerfile
@@ -0,0 +1,72 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages needed by calendar web
+COPY packages/shared-auth ./packages/shared-auth
+COPY packages/shared-auth-ui ./packages/shared-auth-ui
+COPY packages/shared-branding ./packages/shared-branding
+COPY packages/shared-feedback-service ./packages/shared-feedback-service
+COPY packages/shared-feedback-types ./packages/shared-feedback-types
+COPY packages/shared-feedback-ui ./packages/shared-feedback-ui
+COPY packages/shared-i18n ./packages/shared-i18n
+COPY packages/shared-icons ./packages/shared-icons
+COPY packages/shared-tailwind ./packages/shared-tailwind
+COPY packages/shared-theme ./packages/shared-theme
+COPY packages/shared-theme-ui ./packages/shared-theme-ui
+COPY packages/shared-subscription-types ./packages/shared-subscription-types
+COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
+COPY packages/shared-profile-ui ./packages/shared-profile-ui
+COPY packages/shared-ui ./packages/shared-ui
+COPY packages/shared-utils ./packages/shared-utils
+
+# Copy calendar packages and web
+COPY apps/calendar/packages ./apps/calendar/packages
+COPY apps/calendar/apps/web ./apps/calendar/apps/web
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages that need building
+WORKDIR /app/packages/shared-auth
+RUN pnpm build || true
+
+# Build the web app
+WORKDIR /app/apps/calendar/apps/web
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+WORKDIR /app
+
+# Copy built application
+COPY --from=builder /app/apps/calendar/apps/web/build ./build
+COPY --from=builder /app/apps/calendar/apps/web/package.json ./
+
+# Install only production dependencies for the built app
+RUN npm install --omit=dev 2>/dev/null || true
+
+# Expose port
+EXPOSE 5186
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=5186
+ENV HOST=0.0.0.0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:5186/health || exit 1
+
+# Run the app
+CMD ["node", "build"]
diff --git a/apps/clock/apps/backend/Dockerfile b/apps/clock/apps/backend/Dockerfile
new file mode 100644
index 000000000..a2b05c80b
--- /dev/null
+++ b/apps/clock/apps/backend/Dockerfile
@@ -0,0 +1,68 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages
+COPY packages/shared-errors ./packages/shared-errors
+COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
+
+# Copy clock packages and backend
+COPY apps/clock/packages ./apps/clock/packages
+COPY apps/clock/apps/backend ./apps/clock/apps/backend
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages first
+WORKDIR /app/packages/shared-errors
+RUN pnpm build
+
+WORKDIR /app/packages/shared-nestjs-auth
+RUN pnpm build
+
+# Build the backend
+WORKDIR /app/apps/clock/apps/backend
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Install pnpm and postgresql-client for health checks
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate \
+ && apk add --no-cache postgresql-client
+
+WORKDIR /app
+
+# Copy everything from builder (including node_modules)
+COPY --from=builder /app/pnpm-workspace.yaml ./
+COPY --from=builder /app/package.json ./
+COPY --from=builder /app/pnpm-lock.yaml ./
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/packages ./packages
+COPY --from=builder /app/apps/clock ./apps/clock
+
+# Copy entrypoint script
+COPY apps/clock/apps/backend/docker-entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+
+WORKDIR /app/apps/clock/apps/backend
+
+# Expose port
+EXPOSE 3017
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3017/api/v1/health || exit 1
+
+# Run entrypoint script
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["node", "dist/main.js"]
diff --git a/apps/clock/apps/backend/docker-entrypoint.sh b/apps/clock/apps/backend/docker-entrypoint.sh
new file mode 100644
index 000000000..f34aebf6e
--- /dev/null
+++ b/apps/clock/apps/backend/docker-entrypoint.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -e
+
+echo "=== Clock Backend Entrypoint ==="
+
+# Wait for PostgreSQL to be ready
+echo "Waiting for PostgreSQL..."
+until pg_isready -h ${DB_HOST:-postgres} -p ${DB_PORT:-5432} -U ${DB_USER:-postgres} 2>/dev/null; do
+ echo "PostgreSQL is unavailable - sleeping"
+ sleep 2
+done
+echo "PostgreSQL is up!"
+
+cd /app/apps/clock/apps/backend
+
+# Run schema push
+echo "Pushing database schema..."
+npx drizzle-kit push --force
+echo "Schema push completed!"
+
+# Execute the main command
+echo "Starting application..."
+exec "$@"
diff --git a/apps/clock/apps/web/Dockerfile b/apps/clock/apps/web/Dockerfile
new file mode 100644
index 000000000..8b9baf360
--- /dev/null
+++ b/apps/clock/apps/web/Dockerfile
@@ -0,0 +1,72 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages needed by clock web
+COPY packages/shared-auth ./packages/shared-auth
+COPY packages/shared-auth-ui ./packages/shared-auth-ui
+COPY packages/shared-branding ./packages/shared-branding
+COPY packages/shared-feedback-service ./packages/shared-feedback-service
+COPY packages/shared-feedback-types ./packages/shared-feedback-types
+COPY packages/shared-feedback-ui ./packages/shared-feedback-ui
+COPY packages/shared-i18n ./packages/shared-i18n
+COPY packages/shared-icons ./packages/shared-icons
+COPY packages/shared-tailwind ./packages/shared-tailwind
+COPY packages/shared-theme ./packages/shared-theme
+COPY packages/shared-theme-ui ./packages/shared-theme-ui
+COPY packages/shared-subscription-types ./packages/shared-subscription-types
+COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
+COPY packages/shared-profile-ui ./packages/shared-profile-ui
+COPY packages/shared-ui ./packages/shared-ui
+COPY packages/shared-utils ./packages/shared-utils
+
+# Copy clock packages and web
+COPY apps/clock/packages ./apps/clock/packages
+COPY apps/clock/apps/web ./apps/clock/apps/web
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages that need building
+WORKDIR /app/packages/shared-auth
+RUN pnpm build || true
+
+# Build the web app
+WORKDIR /app/apps/clock/apps/web
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+WORKDIR /app
+
+# Copy built application
+COPY --from=builder /app/apps/clock/apps/web/build ./build
+COPY --from=builder /app/apps/clock/apps/web/package.json ./
+
+# Install only production dependencies for the built app
+RUN npm install --omit=dev 2>/dev/null || true
+
+# Expose port
+EXPOSE 5187
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=5187
+ENV HOST=0.0.0.0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:5187/health || exit 1
+
+# Run the app
+CMD ["node", "build"]
diff --git a/apps/manacore/apps/web/Dockerfile b/apps/manacore/apps/web/Dockerfile
new file mode 100644
index 000000000..7fb03759d
--- /dev/null
+++ b/apps/manacore/apps/web/Dockerfile
@@ -0,0 +1,74 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages needed by manacore web
+COPY packages/shared-auth ./packages/shared-auth
+COPY packages/shared-auth-ui ./packages/shared-auth-ui
+COPY packages/shared-branding ./packages/shared-branding
+COPY packages/shared-config ./packages/shared-config
+COPY packages/shared-feedback-service ./packages/shared-feedback-service
+COPY packages/shared-feedback-types ./packages/shared-feedback-types
+COPY packages/shared-feedback-ui ./packages/shared-feedback-ui
+COPY packages/shared-i18n ./packages/shared-i18n
+COPY packages/shared-icons ./packages/shared-icons
+COPY packages/shared-tailwind ./packages/shared-tailwind
+COPY packages/shared-theme ./packages/shared-theme
+COPY packages/shared-theme-ui ./packages/shared-theme-ui
+COPY packages/shared-subscription-types ./packages/shared-subscription-types
+COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
+COPY packages/shared-profile-ui ./packages/shared-profile-ui
+COPY packages/shared-supabase ./packages/shared-supabase
+COPY packages/shared-types ./packages/shared-types
+COPY packages/shared-ui ./packages/shared-ui
+COPY packages/shared-utils ./packages/shared-utils
+
+# Copy manacore web
+COPY apps/manacore/apps/web ./apps/manacore/apps/web
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages that need building
+WORKDIR /app/packages/shared-auth
+RUN pnpm build || true
+
+# Build the web app
+WORKDIR /app/apps/manacore/apps/web
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+WORKDIR /app
+
+# Copy built application
+COPY --from=builder /app/apps/manacore/apps/web/build ./build
+COPY --from=builder /app/apps/manacore/apps/web/package.json ./
+
+# Install only production dependencies for the built app
+RUN npm install --omit=dev 2>/dev/null || true
+
+# Expose port
+EXPOSE 5173
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=5173
+ENV HOST=0.0.0.0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:5173/health || exit 1
+
+# Run the app
+CMD ["node", "build"]
diff --git a/apps/todo/apps/backend/Dockerfile b/apps/todo/apps/backend/Dockerfile
new file mode 100644
index 000000000..e589ef07b
--- /dev/null
+++ b/apps/todo/apps/backend/Dockerfile
@@ -0,0 +1,67 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages
+COPY packages/shared-errors ./packages/shared-errors
+COPY packages/shared-nestjs-auth ./packages/shared-nestjs-auth
+
+# Copy todo backend
+COPY apps/todo/apps/backend ./apps/todo/apps/backend
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages first
+WORKDIR /app/packages/shared-errors
+RUN pnpm build
+
+WORKDIR /app/packages/shared-nestjs-auth
+RUN pnpm build
+
+# Build the backend
+WORKDIR /app/apps/todo/apps/backend
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+# Install pnpm and postgresql-client for health checks
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate \
+ && apk add --no-cache postgresql-client
+
+WORKDIR /app
+
+# Copy everything from builder (including node_modules)
+COPY --from=builder /app/pnpm-workspace.yaml ./
+COPY --from=builder /app/package.json ./
+COPY --from=builder /app/pnpm-lock.yaml ./
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/packages ./packages
+COPY --from=builder /app/apps/todo/apps/backend ./apps/todo/apps/backend
+
+# Copy entrypoint script
+COPY apps/todo/apps/backend/docker-entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/docker-entrypoint.sh
+
+WORKDIR /app/apps/todo/apps/backend
+
+# Expose port
+EXPOSE 3018
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3018/api/v1/health || exit 1
+
+# Run entrypoint script
+ENTRYPOINT ["docker-entrypoint.sh"]
+CMD ["node", "dist/main.js"]
diff --git a/apps/todo/apps/backend/docker-entrypoint.sh b/apps/todo/apps/backend/docker-entrypoint.sh
new file mode 100644
index 000000000..e50cbfd33
--- /dev/null
+++ b/apps/todo/apps/backend/docker-entrypoint.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -e
+
+echo "=== Todo Backend Entrypoint ==="
+
+# Wait for PostgreSQL to be ready
+echo "Waiting for PostgreSQL..."
+until pg_isready -h ${DB_HOST:-postgres} -p ${DB_PORT:-5432} -U ${DB_USER:-postgres} 2>/dev/null; do
+ echo "PostgreSQL is unavailable - sleeping"
+ sleep 2
+done
+echo "PostgreSQL is up!"
+
+cd /app/apps/todo/apps/backend
+
+# Run schema push
+echo "Pushing database schema..."
+npx drizzle-kit push --force
+echo "Schema push completed!"
+
+# Execute the main command
+echo "Starting application..."
+exec "$@"
diff --git a/apps/todo/apps/web/Dockerfile b/apps/todo/apps/web/Dockerfile
new file mode 100644
index 000000000..6d15865e6
--- /dev/null
+++ b/apps/todo/apps/web/Dockerfile
@@ -0,0 +1,72 @@
+# Build stage
+FROM node:20-alpine AS builder
+
+# Install pnpm
+RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
+
+WORKDIR /app
+
+# Copy root workspace files
+COPY pnpm-workspace.yaml ./
+COPY package.json ./
+COPY pnpm-lock.yaml ./
+
+# Copy shared packages needed by todo web
+COPY packages/shared-auth ./packages/shared-auth
+COPY packages/shared-auth-ui ./packages/shared-auth-ui
+COPY packages/shared-branding ./packages/shared-branding
+COPY packages/shared-feedback-service ./packages/shared-feedback-service
+COPY packages/shared-feedback-types ./packages/shared-feedback-types
+COPY packages/shared-feedback-ui ./packages/shared-feedback-ui
+COPY packages/shared-i18n ./packages/shared-i18n
+COPY packages/shared-icons ./packages/shared-icons
+COPY packages/shared-tailwind ./packages/shared-tailwind
+COPY packages/shared-theme ./packages/shared-theme
+COPY packages/shared-theme-ui ./packages/shared-theme-ui
+COPY packages/shared-subscription-types ./packages/shared-subscription-types
+COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
+COPY packages/shared-profile-ui ./packages/shared-profile-ui
+COPY packages/shared-ui ./packages/shared-ui
+COPY packages/shared-utils ./packages/shared-utils
+
+# Copy todo packages and web
+COPY apps/todo/packages ./apps/todo/packages
+COPY apps/todo/apps/web ./apps/todo/apps/web
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Build shared packages that need building
+WORKDIR /app/packages/shared-auth
+RUN pnpm build || true
+
+# Build the web app
+WORKDIR /app/apps/todo/apps/web
+RUN pnpm build
+
+# Production stage
+FROM node:20-alpine AS production
+
+WORKDIR /app
+
+# Copy built application
+COPY --from=builder /app/apps/todo/apps/web/build ./build
+COPY --from=builder /app/apps/todo/apps/web/package.json ./
+
+# Install only production dependencies for the built app
+RUN npm install --omit=dev 2>/dev/null || true
+
+# Expose port
+EXPOSE 5188
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=5188
+ENV HOST=0.0.0.0
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:5188/health || exit 1
+
+# Run the app
+CMD ["node", "build"]
diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml
index 509561d3e..a8d5b5d97 100644
--- a/docker-compose.staging.yml
+++ b/docker-compose.staging.yml
@@ -159,6 +159,242 @@ services:
max-size: "10m"
max-file: "3"
+ # ============================================
+ # Manacore App
+ # ============================================
+
+ manacore-web:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/manacore-web:${MANACORE_WEB_VERSION:-latest}
+ container_name: manacore-web-staging
+ restart: unless-stopped
+ depends_on:
+ mana-core-auth:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 5173
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ ports:
+ - "5173:5173"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5173/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ # ============================================
+ # Todo App
+ # ============================================
+
+ todo-backend:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/todo-backend:${TODO_VERSION:-latest}
+ container_name: todo-backend-staging
+ restart: unless-stopped
+ depends_on:
+ mana-core-auth:
+ condition: service_healthy
+ postgres:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 3018
+ DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/todo
+ DB_HOST: postgres
+ DB_PORT: 5432
+ DB_USER: ${POSTGRES_USER:-postgres}
+ MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ CORS_ORIGINS: http://46.224.108.214:5188,http://localhost:5188
+ ports:
+ - "3018:3018"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3018/api/v1/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ todo-web:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/todo-web:${TODO_WEB_VERSION:-latest}
+ container_name: todo-web-staging
+ restart: unless-stopped
+ depends_on:
+ todo-backend:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 5188
+ PUBLIC_BACKEND_URL: http://todo-backend:3018
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3018
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ ports:
+ - "5188:5188"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5188/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ # ============================================
+ # Calendar App
+ # ============================================
+
+ calendar-backend:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/calendar-backend:${CALENDAR_VERSION:-latest}
+ container_name: calendar-backend-staging
+ restart: unless-stopped
+ depends_on:
+ mana-core-auth:
+ condition: service_healthy
+ postgres:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 3016
+ DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/calendar
+ DB_HOST: postgres
+ DB_PORT: 5432
+ DB_USER: ${POSTGRES_USER:-postgres}
+ MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ CORS_ORIGINS: http://46.224.108.214:5186,http://localhost:5186
+ ports:
+ - "3016:3016"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3016/api/v1/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ calendar-web:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/calendar-web:${CALENDAR_WEB_VERSION:-latest}
+ container_name: calendar-web-staging
+ restart: unless-stopped
+ depends_on:
+ calendar-backend:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 5186
+ PUBLIC_BACKEND_URL: http://calendar-backend:3016
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3016
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ ports:
+ - "5186:5186"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5186/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ # ============================================
+ # Clock App
+ # ============================================
+
+ clock-backend:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/clock-backend:${CLOCK_VERSION:-latest}
+ container_name: clock-backend-staging
+ restart: unless-stopped
+ depends_on:
+ mana-core-auth:
+ condition: service_healthy
+ postgres:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 3017
+ DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD}@postgres:5432/clock
+ DB_HOST: postgres
+ DB_PORT: 5432
+ DB_USER: ${POSTGRES_USER:-postgres}
+ MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ CORS_ORIGINS: http://46.224.108.214:5187,http://localhost:5187
+ ports:
+ - "3017:3017"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3017/api/v1/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
+ clock-web:
+ image: ${DOCKER_REGISTRY:-ghcr.io/memo-2023}/clock-web:${CLOCK_WEB_VERSION:-latest}
+ container_name: clock-web-staging
+ restart: unless-stopped
+ depends_on:
+ clock-backend:
+ condition: service_healthy
+ environment:
+ NODE_ENV: staging
+ PORT: 5187
+ PUBLIC_BACKEND_URL: http://clock-backend:3017
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3017
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ ports:
+ - "5187:5187"
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5187/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
+
# ============================================
# Networks
# ============================================
From 63a5674c0bdd47aad71fcf68977fe0b8120bc774 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 16:15:42 +0100
Subject: [PATCH 08/34] =?UTF-8?q?=F0=9F=90=9B=20fix:=20add=20build=20args?=
=?UTF-8?q?=20for=20SvelteKit=20env=20vars=20in=20web=20Dockerfiles?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
SvelteKit apps using $env/static/public require environment variables
at build time. Added ARG/ENV directives with defaults for Docker builds.
---
apps/calendar/apps/web/Dockerfile | 8 ++++++++
apps/chat/apps/web/Dockerfile | 8 ++++++++
apps/clock/apps/web/Dockerfile | 8 ++++++++
apps/manacore/apps/web/Dockerfile | 6 ++++++
apps/todo/apps/web/Dockerfile | 8 ++++++++
5 files changed, 38 insertions(+)
diff --git a/apps/calendar/apps/web/Dockerfile b/apps/calendar/apps/web/Dockerfile
index 04040c048..94132a760 100644
--- a/apps/calendar/apps/web/Dockerfile
+++ b/apps/calendar/apps/web/Dockerfile
@@ -1,6 +1,14 @@
# Build stage
FROM node:20-alpine AS builder
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_BACKEND_URL=http://calendar-backend:3016
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
diff --git a/apps/chat/apps/web/Dockerfile b/apps/chat/apps/web/Dockerfile
index e6200335f..cd19de658 100644
--- a/apps/chat/apps/web/Dockerfile
+++ b/apps/chat/apps/web/Dockerfile
@@ -1,6 +1,14 @@
# Build stage
FROM node:20-alpine AS builder
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_BACKEND_URL=http://chat-backend:3002
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
diff --git a/apps/clock/apps/web/Dockerfile b/apps/clock/apps/web/Dockerfile
index 8b9baf360..10e51fa63 100644
--- a/apps/clock/apps/web/Dockerfile
+++ b/apps/clock/apps/web/Dockerfile
@@ -1,6 +1,14 @@
# Build stage
FROM node:20-alpine AS builder
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_BACKEND_URL=http://clock-backend:3017
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
diff --git a/apps/manacore/apps/web/Dockerfile b/apps/manacore/apps/web/Dockerfile
index 7fb03759d..3310a6326 100644
--- a/apps/manacore/apps/web/Dockerfile
+++ b/apps/manacore/apps/web/Dockerfile
@@ -1,6 +1,12 @@
# Build stage
FROM node:20-alpine AS builder
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
diff --git a/apps/todo/apps/web/Dockerfile b/apps/todo/apps/web/Dockerfile
index 6d15865e6..740d41725 100644
--- a/apps/todo/apps/web/Dockerfile
+++ b/apps/todo/apps/web/Dockerfile
@@ -1,6 +1,14 @@
# Build stage
FROM node:20-alpine AS builder
+# Build arguments for SvelteKit static env vars
+ARG PUBLIC_BACKEND_URL=http://todo-backend:3018
+ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+
+# Set as environment variables for build
+ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL
+ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
From f0d57c1922d7feeaae6efb90884385a7ac25ca2b Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 16:30:04 +0100
Subject: [PATCH 09/34] =?UTF-8?q?=F0=9F=90=9B=20fix:=20switch=20web=20apps?=
=?UTF-8?q?=20to=20adapter-node=20for=20Docker=20builds?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Change calendar-web, clock-web, todo-web, manacore-web from adapter-auto/netlify to adapter-node
- This ensures the 'build' directory is created for Docker production stage
- adapter-node outputs to 'build' folder which Dockerfiles expect
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/calendar/apps/web/package.json | 2 +-
apps/calendar/apps/web/svelte.config.js | 6 +-
apps/clock/apps/web/package.json | 2 +-
apps/clock/apps/web/svelte.config.js | 6 +-
apps/manacore/apps/web/package.json | 3 +-
apps/manacore/apps/web/svelte.config.js | 6 +-
apps/todo/apps/web/package.json | 2 +-
apps/todo/apps/web/svelte.config.js | 6 +-
pnpm-lock.yaml | 922 ++++++++++++++----------
9 files changed, 575 insertions(+), 380 deletions(-)
diff --git a/apps/calendar/apps/web/package.json b/apps/calendar/apps/web/package.json
index d76fb665d..4f71acf61 100644
--- a/apps/calendar/apps/web/package.json
+++ b/apps/calendar/apps/web/package.json
@@ -13,7 +13,7 @@
"type-check": "echo 'Skipping type-check for now'"
},
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
+ "@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.1.7",
diff --git a/apps/calendar/apps/web/svelte.config.js b/apps/calendar/apps/web/svelte.config.js
index c8b303bb6..a7a917e4c 100644
--- a/apps/calendar/apps/web/svelte.config.js
+++ b/apps/calendar/apps/web/svelte.config.js
@@ -1,11 +1,13 @@
-import adapter from '@sveltejs/adapter-auto';
+import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
- adapter: adapter(),
+ adapter: adapter({
+ out: 'build',
+ }),
},
};
diff --git a/apps/clock/apps/web/package.json b/apps/clock/apps/web/package.json
index c6ae5a7cf..b279bf2c2 100644
--- a/apps/clock/apps/web/package.json
+++ b/apps/clock/apps/web/package.json
@@ -13,7 +13,7 @@
"type-check": "echo 'Skipping type-check for now'"
},
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
+ "@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.1.7",
diff --git a/apps/clock/apps/web/svelte.config.js b/apps/clock/apps/web/svelte.config.js
index c8b303bb6..a7a917e4c 100644
--- a/apps/clock/apps/web/svelte.config.js
+++ b/apps/clock/apps/web/svelte.config.js
@@ -1,11 +1,13 @@
-import adapter from '@sveltejs/adapter-auto';
+import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
- adapter: adapter(),
+ adapter: adapter({
+ out: 'build',
+ }),
},
};
diff --git a/apps/manacore/apps/web/package.json b/apps/manacore/apps/web/package.json
index ae3c273cf..8c019c6dc 100644
--- a/apps/manacore/apps/web/package.json
+++ b/apps/manacore/apps/web/package.json
@@ -16,8 +16,7 @@
},
"devDependencies": {
"@playwright/test": "^1.51.0",
- "@sveltejs/adapter-auto": "^7.0.0",
- "@sveltejs/adapter-netlify": "^5.2.4",
+ "@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/kit": "^2.15.7",
"@sveltejs/vite-plugin-svelte": "^5.0.4",
"@tailwindcss/postcss": "^4.1.17",
diff --git a/apps/manacore/apps/web/svelte.config.js b/apps/manacore/apps/web/svelte.config.js
index c797b64e2..632e2e7c9 100644
--- a/apps/manacore/apps/web/svelte.config.js
+++ b/apps/manacore/apps/web/svelte.config.js
@@ -1,4 +1,4 @@
-import adapter from '@sveltejs/adapter-netlify';
+import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
@@ -6,7 +6,9 @@ const config = {
preprocess: vitePreprocess(),
kit: {
- adapter: adapter(),
+ adapter: adapter({
+ out: 'build',
+ }),
alias: {
$lib: 'src/lib',
$components: 'src/lib/components',
diff --git a/apps/todo/apps/web/package.json b/apps/todo/apps/web/package.json
index b3abfd6d0..2d79f2a44 100644
--- a/apps/todo/apps/web/package.json
+++ b/apps/todo/apps/web/package.json
@@ -13,7 +13,7 @@
"type-check": "echo 'Skipping type-check for now'"
},
"devDependencies": {
- "@sveltejs/adapter-auto": "^3.0.0",
+ "@sveltejs/adapter-node": "^5.0.0",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/vite": "^4.1.7",
diff --git a/apps/todo/apps/web/svelte.config.js b/apps/todo/apps/web/svelte.config.js
index c8b303bb6..a7a917e4c 100644
--- a/apps/todo/apps/web/svelte.config.js
+++ b/apps/todo/apps/web/svelte.config.js
@@ -1,11 +1,13 @@
-import adapter from '@sveltejs/adapter-auto';
+import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
- adapter: adapter(),
+ adapter: adapter({
+ out: 'build',
+ }),
},
};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 66a999cfb..f21357ea4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -116,7 +116,7 @@ importers:
devDependencies:
'@nestjs/cli':
specifier: ^10.4.9
- version: 10.4.9(esbuild@0.27.0)
+ version: 10.4.9(esbuild@0.19.12)
'@nestjs/schematics':
specifier: ^10.2.3
version: 10.2.3(chokidar@3.6.0)(typescript@5.9.3)
@@ -149,7 +149,7 @@ importers:
version: 0.5.21
ts-loader:
specifier: ^9.5.1
- version: 9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0))
+ version: 9.5.4(typescript@5.9.3)(webpack@5.97.1(esbuild@0.19.12))
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
@@ -173,14 +173,14 @@ importers:
version: link:../../../../packages/shared-landing-ui
astro:
specifier: ^5.16.0
- version: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
+ version: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
typescript:
specifier: ^5.9.2
version: 5.9.3
devDependencies:
'@astrojs/tailwind':
specifier: ^6.0.2
- version: 6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))
+ version: 6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))
'@tailwindcss/typography':
specifier: ^0.5.18
version: 0.5.19(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))
@@ -189,13 +189,13 @@ importers:
version: 20.19.25
eslint:
specifier: ^9.0.0
- version: 9.39.1(jiti@2.6.1)
+ version: 9.39.1(jiti@1.21.7)
eslint-config-prettier:
specifier: ^9.1.0
- version: 9.1.2(eslint@9.39.1(jiti@2.6.1))
+ version: 9.1.2(eslint@9.39.1(jiti@1.21.7))
eslint-plugin-astro:
specifier: ^1.0.0
- version: 1.5.0(eslint@9.39.1(jiti@2.6.1))
+ version: 1.5.0(eslint@9.39.1(jiti@1.21.7))
prettier:
specifier: ^3.6.2
version: 3.6.2
@@ -266,9 +266,9 @@ importers:
specifier: ^4.0.1
version: 4.0.1(svelte@5.44.0)
devDependencies:
- '@sveltejs/adapter-auto':
- specifier: ^3.0.0
- version: 3.3.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
+ '@sveltejs/adapter-node':
+ specifier: ^5.0.0
+ version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
'@sveltejs/kit':
specifier: ^2.0.0
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
@@ -528,19 +528,19 @@ importers:
version: 18.3.27
'@typescript-eslint/eslint-plugin':
specifier: ^7.7.0
- version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/parser':
specifier: ^7.7.0
- version: 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ version: 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
dotenv:
specifier: ^16.4.7
version: 16.6.1
eslint:
specifier: ^9.39.1
- version: 9.39.1(jiti@1.21.7)
+ version: 9.39.1(jiti@2.6.1)
eslint-config-universe:
specifier: ^12.0.1
- version: 12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)(typescript@5.3.3)
+ version: 12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)(typescript@5.3.3)
prettier:
specifier: ^3.2.5
version: 3.6.2
@@ -856,9 +856,9 @@ importers:
specifier: ^3.1.0
version: 3.1.0
devDependencies:
- '@sveltejs/adapter-auto':
- specifier: ^3.0.0
- version: 3.3.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
+ '@sveltejs/adapter-node':
+ specifier: ^5.0.0
+ version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
'@sveltejs/kit':
specifier: ^2.0.0
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
@@ -1479,12 +1479,9 @@ importers:
'@playwright/test':
specifier: ^1.51.0
version: 1.57.0
- '@sveltejs/adapter-auto':
- specifier: ^7.0.0
- version: 7.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
- '@sveltejs/adapter-netlify':
- specifier: ^5.2.4
- version: 5.2.4(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
+ '@sveltejs/adapter-node':
+ specifier: ^5.0.0
+ version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
'@sveltejs/kit':
specifier: ^2.15.7
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
@@ -2611,9 +2608,9 @@ importers:
specifier: ^4.0.1
version: 4.0.1(svelte@5.44.0)
devDependencies:
- '@sveltejs/adapter-auto':
- specifier: ^3.0.0
- version: 3.3.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
+ '@sveltejs/adapter-node':
+ specifier: ^5.0.0
+ version: 5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))
'@sveltejs/kit':
specifier: ^2.0.0
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
@@ -4159,7 +4156,7 @@ importers:
version: 1.57.0
jest:
specifier: ^29.0.0
- version: 29.7.0(@types/node@24.10.1)
+ version: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
@@ -4279,7 +4276,7 @@ importers:
devDependencies:
'@nestjs/cli':
specifier: ^11.0.0
- version: 11.0.12(@types/node@22.19.1)(esbuild@0.19.12)
+ version: 11.0.12(@types/node@22.19.1)
'@nestjs/schematics':
specifier: ^11.0.0
version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3)
@@ -4333,10 +4330,10 @@ importers:
version: 7.1.4
ts-jest:
specifier: ^29.2.5
- version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3)
+ version: 29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3)
ts-loader:
specifier: ^9.5.1
- version: 9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.19.12))
+ version: 9.5.4(typescript@5.9.3)(webpack@5.100.2)
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
@@ -6551,7 +6548,7 @@ packages:
'@expo/bunyan@4.0.1':
resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==}
- engines: {node: '>=0.10.0'}
+ engines: {'0': node >=0.10.0}
'@expo/cli@0.22.26':
resolution: {integrity: sha512-I689wc8Fn/AX7aUGiwrh3HnssiORMJtR2fpksX+JIe8Cj/EDleblYMSwRPd0025wrwOV9UN1KM/RuEt/QjCS3Q==}
@@ -19713,6 +19710,16 @@ snapshots:
transitivePeerDependencies:
- ts-node
+ '@astrojs/tailwind@6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))':
+ dependencies:
+ astro: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
+ autoprefixer: 10.4.22(postcss@8.5.6)
+ postcss: 8.5.6
+ postcss-load-config: 4.0.2(postcss@8.5.6)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))
+ tailwindcss: 3.4.18(tsx@4.20.6)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - ts-node
+
'@astrojs/tailwind@6.0.2(astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1))(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))':
dependencies:
astro: 5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1)
@@ -22226,7 +22233,7 @@ snapshots:
wrap-ansi: 7.0.0
ws: 8.18.3
optionalDependencies:
- expo-router: 6.0.15(jiucxy5ca3jdtbnulaxuc46jdq)
+ expo-router: 6.0.15(5e7ih2rh6mb55wruwvjljgzihq)
react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
@@ -22303,7 +22310,7 @@ snapshots:
wrap-ansi: 7.0.0
ws: 8.18.3
optionalDependencies:
- expo-router: 6.0.15(ohit2up6tuxb3x34brxduivol4)
+ expo-router: 6.0.15(nttrd3tw67nnyhowcwgdzipb5e)
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
@@ -23538,41 +23545,6 @@ snapshots:
jest-util: 30.2.0
slash: 3.0.0
- '@jest/core@29.7.0':
- dependencies:
- '@jest/console': 29.7.0
- '@jest/reporters': 29.7.0
- '@jest/test-result': 29.7.0
- '@jest/transform': 29.7.0
- '@jest/types': 29.6.3
- '@types/node': 22.19.1
- ansi-escapes: 4.3.2
- chalk: 4.1.2
- ci-info: 3.9.0
- exit: 0.1.2
- graceful-fs: 4.2.11
- jest-changed-files: 29.7.0
- jest-config: 29.7.0(@types/node@22.19.1)
- jest-haste-map: 29.7.0
- jest-message-util: 29.7.0
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-resolve-dependencies: 29.7.0
- jest-runner: 29.7.0
- jest-runtime: 29.7.0
- jest-snapshot: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.7.0
- jest-watcher: 29.7.0
- micromatch: 4.0.8
- pretty-format: 29.7.0
- slash: 3.0.0
- strip-ansi: 6.0.1
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
- - ts-node
-
'@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))':
dependencies:
'@jest/console': 29.7.0
@@ -23608,6 +23580,78 @@ snapshots:
- supports-color
- ts-node
+ '@jest/core@29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))':
+ dependencies:
+ '@jest/console': 29.7.0
+ '@jest/reporters': 29.7.0
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.1
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ jest-changed-files: 29.7.0
+ jest-config: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
+ jest-haste-map: 29.7.0
+ jest-message-util: 29.7.0
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-resolve-dependencies: 29.7.0
+ jest-runner: 29.7.0
+ jest-runtime: 29.7.0
+ jest-snapshot: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ jest-watcher: 29.7.0
+ micromatch: 4.0.8
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-ansi: 6.0.1
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ '@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))':
+ dependencies:
+ '@jest/console': 30.2.0
+ '@jest/pattern': 30.0.1
+ '@jest/reporters': 30.2.0
+ '@jest/test-result': 30.2.0
+ '@jest/transform': 30.2.0
+ '@jest/types': 30.2.0
+ '@types/node': 22.19.1
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ ci-info: 4.3.1
+ exit-x: 0.2.2
+ graceful-fs: 4.2.11
+ jest-changed-files: 30.2.0
+ jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ jest-haste-map: 30.2.0
+ jest-message-util: 30.2.0
+ jest-regex-util: 30.0.1
+ jest-resolve: 30.2.0
+ jest-resolve-dependencies: 30.2.0
+ jest-runner: 30.2.0
+ jest-runtime: 30.2.0
+ jest-snapshot: 30.2.0
+ jest-util: 30.2.0
+ jest-validate: 30.2.0
+ jest-watcher: 30.2.0
+ micromatch: 4.0.8
+ pretty-format: 30.2.0
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - esbuild-register
+ - supports-color
+ - ts-node
+ optional: true
+
'@jest/core@30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))':
dependencies:
'@jest/console': 30.2.0
@@ -24032,6 +24076,32 @@ snapshots:
- uglify-js
- webpack-cli
+ '@nestjs/cli@10.4.9(esbuild@0.19.12)':
+ dependencies:
+ '@angular-devkit/core': 17.3.11(chokidar@3.6.0)
+ '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0)
+ '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0)
+ '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.7.2)
+ chalk: 4.1.2
+ chokidar: 3.6.0
+ cli-table3: 0.6.5
+ commander: 4.1.1
+ fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.19.12))
+ glob: 10.4.5
+ inquirer: 8.2.6
+ node-emoji: 1.11.0
+ ora: 5.4.1
+ tree-kill: 1.2.2
+ tsconfig-paths: 4.2.0
+ tsconfig-paths-webpack-plugin: 4.2.0
+ typescript: 5.7.2
+ webpack: 5.97.1(esbuild@0.19.12)
+ webpack-node-externals: 3.0.0
+ transitivePeerDependencies:
+ - esbuild
+ - uglify-js
+ - webpack-cli
+
'@nestjs/cli@10.4.9(esbuild@0.27.0)':
dependencies:
'@angular-devkit/core': 17.3.11(chokidar@3.6.0)
@@ -24058,7 +24128,7 @@ snapshots:
- uglify-js
- webpack-cli
- '@nestjs/cli@11.0.12(@types/node@22.19.1)(esbuild@0.19.12)':
+ '@nestjs/cli@11.0.12(@types/node@22.19.1)':
dependencies:
'@angular-devkit/core': 19.2.19(chokidar@4.0.3)
'@angular-devkit/schematics': 19.2.19(chokidar@4.0.3)
@@ -24069,14 +24139,14 @@ snapshots:
chokidar: 4.0.3
cli-table3: 0.6.5
commander: 4.1.1
- fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.100.2(esbuild@0.19.12))
+ fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.100.2)
glob: 12.0.0
node-emoji: 1.11.0
ora: 5.4.1
tsconfig-paths: 4.2.0
tsconfig-paths-webpack-plugin: 4.2.0
typescript: 5.9.3
- webpack: 5.100.2(esbuild@0.19.12)
+ webpack: 5.100.2
webpack-node-externals: 3.0.0
transitivePeerDependencies:
- '@types/node'
@@ -26211,21 +26281,10 @@ snapshots:
dependencies:
'@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
- '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
- dependencies:
- '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
-
'@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
dependencies:
'@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
- '@sveltejs/adapter-netlify@5.2.4(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
- dependencies:
- '@iarna/toml': 2.2.5
- '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
- esbuild: 0.25.12
- set-cookie-parser: 2.7.2
-
'@sveltejs/adapter-netlify@5.2.4(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
dependencies:
'@iarna/toml': 2.2.5
@@ -26233,6 +26292,22 @@ snapshots:
esbuild: 0.25.12
set-cookie-parser: 2.7.2
+ '@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
+ dependencies:
+ '@rollup/plugin-commonjs': 28.0.9(rollup@4.53.3)
+ '@rollup/plugin-json': 6.1.0(rollup@4.53.3)
+ '@rollup/plugin-node-resolve': 16.0.3(rollup@4.53.3)
+ '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
+ rollup: 4.53.3
+
+ '@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
+ dependencies:
+ '@rollup/plugin-commonjs': 28.0.9(rollup@4.53.3)
+ '@rollup/plugin-json': 6.1.0(rollup@4.53.3)
+ '@rollup/plugin-node-resolve': 16.0.3(rollup@4.53.3)
+ '@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
+ rollup: 4.53.3
+
'@sveltejs/adapter-node@5.4.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))(svelte@5.44.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)))':
dependencies:
'@rollup/plugin-commonjs': 28.0.9(rollup@4.53.3)
@@ -26631,7 +26706,7 @@ snapshots:
react-test-renderer: 19.1.0(react@19.1.0)
redent: 3.0.0
- '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
+ '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
jest-matcher-utils: 30.2.0
picocolors: 1.1.1
@@ -26641,20 +26716,7 @@ snapshots:
react-test-renderer: 19.1.0(react@19.1.0)
redent: 3.0.0
optionalDependencies:
- jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
- optional: true
-
- '@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
- dependencies:
- jest-matcher-utils: 30.2.0
- picocolors: 1.1.1
- pretty-format: 30.2.0
- react: 19.1.0
- react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
- react-test-renderer: 19.1.0(react@19.1.0)
- redent: 3.0.0
- optionalDependencies:
- jest: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
+ jest: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
optional: true
'@testing-library/react-native@13.3.3(jest@30.2.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)':
@@ -27168,16 +27230,16 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/scope-manager': 6.21.0
- '@typescript-eslint/type-utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/type-utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
graphemer: 1.4.0
ignore: 5.3.2
natural-compare: 1.4.0
@@ -27226,15 +27288,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/parser': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/scope-manager': 7.18.0
- '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 7.18.0
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
graphemer: 1.4.0
ignore: 5.3.2
natural-compare: 1.4.0
@@ -27326,14 +27388,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
optionalDependencies:
typescript: 5.3.3
transitivePeerDependencies:
@@ -27365,14 +27427,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@typescript-eslint/scope-manager': 7.18.0
'@typescript-eslint/types': 7.18.0
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 7.18.0
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
optionalDependencies:
typescript: 5.3.3
transitivePeerDependencies:
@@ -27498,12 +27560,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/type-utils@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/type-utils@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
- '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
ts-api-utils: 1.4.3(typescript@5.3.3)
optionalDependencies:
typescript: 5.3.3
@@ -27534,12 +27596,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/type-utils@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/type-utils@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3)
- '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
debug: 4.4.3
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
ts-api-utils: 1.4.3(typescript@5.3.3)
optionalDependencies:
typescript: 5.3.3
@@ -27721,15 +27783,15 @@ snapshots:
- supports-color
- typescript
- '@typescript-eslint/utils@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/utils@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7))
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
'@types/json-schema': 7.0.15
'@types/semver': 7.7.1
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
semver: 7.7.3
transitivePeerDependencies:
- supports-color
@@ -27760,13 +27822,13 @@ snapshots:
- supports-color
- typescript
- '@typescript-eslint/utils@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)':
+ '@typescript-eslint/utils@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7))
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
'@typescript-eslint/scope-manager': 7.18.0
'@typescript-eslint/types': 7.18.0
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3)
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
transitivePeerDependencies:
- supports-color
- typescript
@@ -28567,6 +28629,108 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1):
+ dependencies:
+ '@astrojs/compiler': 2.13.0
+ '@astrojs/internal-helpers': 0.7.5
+ '@astrojs/markdown-remark': 6.3.9
+ '@astrojs/telemetry': 3.3.0
+ '@capsizecss/unpack': 3.0.1
+ '@oslojs/encoding': 1.1.0
+ '@rollup/pluginutils': 5.3.0(rollup@4.53.3)
+ acorn: 8.15.0
+ aria-query: 5.3.2
+ axobject-query: 4.1.0
+ boxen: 8.0.1
+ ci-info: 4.3.1
+ clsx: 2.1.1
+ common-ancestor-path: 1.0.1
+ cookie: 1.1.0
+ cssesc: 3.0.0
+ debug: 4.4.3
+ deterministic-object-hash: 2.0.2
+ devalue: 5.5.0
+ diff: 5.2.0
+ dlv: 1.1.3
+ dset: 3.1.4
+ es-module-lexer: 1.7.0
+ esbuild: 0.25.12
+ estree-walker: 3.0.3
+ flattie: 1.1.1
+ fontace: 0.3.1
+ github-slugger: 2.0.0
+ html-escaper: 3.0.3
+ http-cache-semantics: 4.2.0
+ import-meta-resolve: 4.2.0
+ js-yaml: 4.1.1
+ magic-string: 0.30.21
+ magicast: 0.5.1
+ mrmime: 2.0.1
+ neotraverse: 0.6.18
+ p-limit: 6.2.0
+ p-queue: 8.1.1
+ package-manager-detector: 1.5.0
+ piccolore: 0.1.3
+ picomatch: 4.0.3
+ prompts: 2.4.2
+ rehype: 13.0.2
+ semver: 7.7.3
+ shiki: 3.15.0
+ smol-toml: 1.5.2
+ svgo: 4.0.0
+ tinyexec: 1.0.2
+ tinyglobby: 0.2.15
+ tsconfck: 3.1.6(typescript@5.9.3)
+ ultrahtml: 1.6.0
+ unifont: 0.6.0
+ unist-util-visit: 5.0.0
+ unstorage: 1.17.3(@netlify/blobs@10.4.1)(ioredis@5.8.2)
+ vfile: 6.0.3
+ vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
+ vitefu: 1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1))
+ xxhash-wasm: 1.1.0
+ yargs-parser: 21.1.1
+ yocto-spinner: 0.2.3
+ zod: 3.25.76
+ zod-to-json-schema: 3.25.0(zod@3.25.76)
+ zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76)
+ optionalDependencies:
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@azure/app-configuration'
+ - '@azure/cosmos'
+ - '@azure/data-tables'
+ - '@azure/identity'
+ - '@azure/keyvault-secrets'
+ - '@azure/storage-blob'
+ - '@capacitor/preferences'
+ - '@deno/kv'
+ - '@netlify/blobs'
+ - '@planetscale/database'
+ - '@types/node'
+ - '@upstash/redis'
+ - '@vercel/blob'
+ - '@vercel/functions'
+ - '@vercel/kv'
+ - aws4fetch
+ - db0
+ - idb-keyval
+ - ioredis
+ - jiti
+ - less
+ - lightningcss
+ - rollup
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - typescript
+ - uploadthing
+ - yaml
+
astro@5.16.0(@netlify/blobs@10.4.1)(@types/node@20.19.25)(ioredis@5.8.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.20.6)(typescript@5.9.3)(yaml@2.8.1):
dependencies:
'@astrojs/compiler': 2.13.0
@@ -29788,13 +29952,13 @@ snapshots:
- supports-color
- ts-node
- create-jest@29.7.0(@types/node@24.10.1):
+ create-jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
dependencies:
'@jest/types': 29.6.3
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
- jest-config: 29.7.0(@types/node@24.10.1)
+ jest-config: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@@ -30852,6 +31016,11 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
+ eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@1.21.7)):
+ dependencies:
+ eslint: 9.39.1(jiti@1.21.7)
+ semver: 7.7.3
+
eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -30862,9 +31031,9 @@ snapshots:
'@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.1(jiti@2.6.1)
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-expo: 1.0.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1))
globals: 16.5.0
@@ -30879,9 +31048,9 @@ snapshots:
'@typescript-eslint/eslint-plugin': 8.48.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
eslint: 9.39.1(jiti@2.6.1)
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-expo: 0.1.4(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.1(jiti@2.6.1))
globals: 16.5.0
@@ -30899,14 +31068,14 @@ snapshots:
dependencies:
eslint: 8.57.1
- eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
-
eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
+ eslint-config-prettier@9.1.2(eslint@9.39.1(jiti@1.21.7)):
+ dependencies:
+ eslint: 9.39.1(jiti@1.21.7)
+
eslint-config-prettier@9.1.2(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -30931,17 +31100,17 @@ snapshots:
- supports-color
- typescript
- eslint-config-universe@12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)(typescript@5.3.3):
+ eslint-config-universe@12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)(typescript@5.3.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- eslint: 9.39.1(jiti@1.21.7)
- eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@1.21.7))
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))
- eslint-plugin-node: 11.1.0(eslint@9.39.1(jiti@1.21.7))
- eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)
- eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7))
- eslint-plugin-react-hooks: 4.6.2(eslint@9.39.1(jiti@1.21.7))
+ '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ eslint: 9.39.1(jiti@2.6.1)
+ eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-node: 11.1.0(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)
+ eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-react-hooks: 4.6.2(eslint@9.39.1(jiti@2.6.1))
optionalDependencies:
prettier: 3.6.2
transitivePeerDependencies:
@@ -30979,7 +31148,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1)):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.3
@@ -30990,7 +31159,22 @@ snapshots:
tinyglobby: 0.2.15
unrs-resolver: 1.11.1
optionalDependencies:
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
+ dependencies:
+ '@nolyfill/is-core-module': 1.0.39
+ debug: 4.4.3
+ eslint: 9.39.1(jiti@2.6.1)
+ get-tsconfig: 4.13.0
+ is-bun-module: 2.0.0
+ stable-hash: 0.0.5
+ tinyglobby: 0.2.15
+ unrs-resolver: 1.11.1
+ optionalDependencies:
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
@@ -31004,12 +31188,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@1.21.7)):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
- '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
- eslint: 9.39.1(jiti@1.21.7)
+ '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
+ eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
transitivePeerDependencies:
- supports-color
@@ -31024,25 +31208,39 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3)
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@2.6.1))
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-astro@1.5.0(eslint@9.39.1(jiti@1.21.7)):
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7))
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@typescript-eslint/types': 8.48.0
+ astro-eslint-parser: 1.2.2
+ eslint: 9.39.1(jiti@1.21.7)
+ eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7))
+ globals: 16.5.0
+ postcss: 8.5.6
+ postcss-selector-parser: 7.1.0
transitivePeerDependencies:
- supports-color
@@ -31066,12 +31264,6 @@ snapshots:
eslint-utils: 2.1.0
regexpp: 3.2.0
- eslint-plugin-es@3.0.1(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
- eslint-utils: 2.1.0
- regexpp: 3.2.0
-
eslint-plugin-es@3.0.1(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -31125,7 +31317,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7)):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -31134,9 +31326,9 @@ snapshots:
array.prototype.flatmap: 1.3.3
debug: 3.2.7
doctrine: 2.1.0
- eslint: 9.39.1(jiti@1.21.7)
+ eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@1.21.7))
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -31148,7 +31340,7 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)
+ '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@@ -31183,7 +31375,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -31194,7 +31386,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.8.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -31212,7 +31404,7 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1)):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -31223,7 +31415,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.1(jiti@2.6.1)
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@2.6.1))
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.48.1(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))
hasown: 2.0.2
is-core-module: 2.16.1
is-glob: 4.0.3
@@ -31251,16 +31443,6 @@ snapshots:
resolve: 1.22.11
semver: 6.3.1
- eslint-plugin-node@11.1.0(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
- eslint-plugin-es: 3.0.1(eslint@9.39.1(jiti@1.21.7))
- eslint-utils: 2.1.0
- ignore: 5.3.2
- minimatch: 3.1.2
- resolve: 1.22.11
- semver: 6.3.1
-
eslint-plugin-node@11.1.0(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -31291,16 +31473,6 @@ snapshots:
'@types/eslint': 9.6.1
eslint-config-prettier: 8.10.2(eslint@8.57.1)
- eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
- prettier: 3.6.2
- prettier-linter-helpers: 1.0.0
- synckit: 0.11.11
- optionalDependencies:
- '@types/eslint': 9.6.1
- eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@1.21.7))
-
eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -31325,10 +31497,6 @@ snapshots:
dependencies:
eslint: 8.57.1
- eslint-plugin-react-hooks@4.6.2(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- eslint: 9.39.1(jiti@1.21.7)
-
eslint-plugin-react-hooks@4.6.2(eslint@9.39.1(jiti@2.6.1)):
dependencies:
eslint: 9.39.1(jiti@2.6.1)
@@ -31359,28 +31527,6 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
- eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@1.21.7)):
- dependencies:
- array-includes: 3.1.9
- array.prototype.findlast: 1.2.5
- array.prototype.flatmap: 1.3.3
- array.prototype.tosorted: 1.1.4
- doctrine: 2.1.0
- es-iterator-helpers: 1.2.1
- eslint: 9.39.1(jiti@1.21.7)
- estraverse: 5.3.0
- hasown: 2.0.2
- jsx-ast-utils: 3.3.5
- minimatch: 3.1.2
- object.entries: 1.1.9
- object.fromentries: 2.0.8
- object.values: 1.2.1
- prop-types: 15.8.1
- resolve: 2.0.0-next.5
- semver: 6.3.1
- string.prototype.matchall: 4.0.12
- string.prototype.repeat: 1.0.0
-
eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@2.6.1)):
dependencies:
array-includes: 3.1.9
@@ -32500,54 +32646,7 @@ snapshots:
- '@types/react-dom'
- supports-color
- expo-router@6.0.15(jiucxy5ca3jdtbnulaxuc46jdq):
- dependencies:
- '@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@expo/schema-utils': 0.1.7
- '@radix-ui/react-slot': 1.2.0(@types/react@19.2.7)(react@19.1.0)
- '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- '@react-navigation/bottom-tabs': 7.8.6(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@react-navigation/native': 7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@react-navigation/native-stack': 7.8.0(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- client-only: 0.0.1
- debug: 4.4.3
- escape-string-regexp: 4.0.0
- expo: 54.0.25(@babel/core@7.28.5)(@expo/metro-runtime@6.1.2)(expo-router@6.0.15)(react-native-webview@13.12.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- expo-constants: 18.0.10(expo@54.0.25)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))
- expo-linking: 8.0.9(expo@54.0.25)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- expo-server: 1.0.4
- fast-deep-equal: 3.1.3
- invariant: 2.2.4
- nanoid: 3.3.11
- query-string: 7.1.3
- react: 19.1.0
- react-fast-compare: 3.2.2
- react-native: 0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0)
- react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- react-native-safe-area-context: 5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- react-native-screens: 4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- semver: 7.6.3
- server-only: 0.0.1
- sf-symbols-typescript: 2.1.0
- shallowequal: 1.1.0
- use-latest-callback: 0.2.6(react@19.1.0)
- vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- optionalDependencies:
- '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)
- react-dom: 19.1.0(react@19.1.0)
- react-native-gesture-handler: 2.28.0(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0))
- transitivePeerDependencies:
- - '@react-native-masked-view/masked-view'
- - '@types/react'
- - '@types/react-dom'
- - supports-color
- optional: true
-
- expo-router@6.0.15(ohit2up6tuxb3x34brxduivol4):
+ expo-router@6.0.15(nttrd3tw67nnyhowcwgdzipb5e):
dependencies:
'@expo/metro-runtime': 6.1.2(expo@54.0.25)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
'@expo/schema-utils': 0.1.7
@@ -32581,12 +32680,12 @@ snapshots:
vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
optionalDependencies:
'@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
- '@testing-library/react-native': 13.3.3(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)
+ '@testing-library/react-native': 13.3.3(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)
react-dom: 19.1.0(react@19.1.0)
react-native-gesture-handler: 2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.6.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.2.7)(react@19.1.0))(react@19.1.0)
react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.19.12))
+ react-server-dom-webpack: 19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.97.1(esbuild@0.19.12))
transitivePeerDependencies:
- '@react-native-masked-view/masked-view'
- '@types/react'
@@ -33427,6 +33526,23 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
+ fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.19.12)):
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ chalk: 4.1.2
+ chokidar: 3.6.0
+ cosmiconfig: 8.3.6(typescript@5.7.2)
+ deepmerge: 4.3.1
+ fs-extra: 10.1.0
+ memfs: 3.5.3
+ minimatch: 3.1.2
+ node-abort-controller: 3.1.1
+ schema-utils: 3.3.0
+ semver: 7.7.3
+ tapable: 2.3.0
+ typescript: 5.7.2
+ webpack: 5.97.1(esbuild@0.19.12)
+
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.7.2)(webpack@5.97.1(esbuild@0.27.0)):
dependencies:
'@babel/code-frame': 7.27.1
@@ -33461,23 +33577,6 @@ snapshots:
typescript: 5.7.2
webpack: 5.97.1
- fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.100.2(esbuild@0.19.12)):
- dependencies:
- '@babel/code-frame': 7.27.1
- chalk: 4.1.2
- chokidar: 4.0.3
- cosmiconfig: 8.3.6(typescript@5.9.3)
- deepmerge: 4.3.1
- fs-extra: 10.1.0
- memfs: 3.5.3
- minimatch: 3.1.2
- node-abort-controller: 3.1.1
- schema-utils: 3.3.0
- semver: 7.7.3
- tapable: 2.3.0
- typescript: 5.9.3
- webpack: 5.100.2(esbuild@0.19.12)
-
fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0)):
dependencies:
'@babel/code-frame': 7.27.1
@@ -33495,6 +33594,23 @@ snapshots:
typescript: 5.9.3
webpack: 5.100.2(esbuild@0.27.0)
+ fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.100.2):
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ chalk: 4.1.2
+ chokidar: 4.0.3
+ cosmiconfig: 8.3.6(typescript@5.9.3)
+ deepmerge: 4.3.1
+ fs-extra: 10.1.0
+ memfs: 3.5.3
+ minimatch: 3.1.2
+ node-abort-controller: 3.1.1
+ schema-utils: 3.3.0
+ semver: 7.7.3
+ tapable: 2.3.0
+ typescript: 5.9.3
+ webpack: 5.100.2
+
form-data-encoder@1.7.2: {}
form-data@3.0.4:
@@ -34661,16 +34777,16 @@ snapshots:
- supports-color
- ts-node
- jest-cli@29.7.0(@types/node@24.10.1):
+ jest-cli@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 29.7.0
+ '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
- create-jest: 29.7.0(@types/node@24.10.1)
+ create-jest: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
exit: 0.1.2
import-local: 3.2.0
- jest-config: 29.7.0(@types/node@24.10.1)
+ jest-config: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@@ -34680,15 +34796,15 @@ snapshots:
- supports-color
- ts-node
- jest-cli@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
+ jest-cli@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))
+ '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
'@jest/test-result': 30.2.0
'@jest/types': 30.2.0
chalk: 4.1.2
exit-x: 0.2.2
import-local: 3.2.0
- jest-config: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
+ jest-config: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
jest-util: 30.2.0
jest-validate: 30.2.0
yargs: 17.7.2
@@ -34758,36 +34874,6 @@ snapshots:
- ts-node
optional: true
- jest-config@29.7.0(@types/node@22.19.1):
- dependencies:
- '@babel/core': 7.28.5
- '@jest/test-sequencer': 29.7.0
- '@jest/types': 29.6.3
- babel-jest: 29.7.0(@babel/core@7.28.5)
- chalk: 4.1.2
- ci-info: 3.9.0
- deepmerge: 4.3.1
- glob: 7.2.3
- graceful-fs: 4.2.11
- jest-circus: 29.7.0
- jest-environment-node: 29.7.0
- jest-get-type: 29.6.3
- jest-regex-util: 29.6.3
- jest-resolve: 29.7.0
- jest-runner: 29.7.0
- jest-util: 29.7.0
- jest-validate: 29.7.0
- micromatch: 4.0.8
- parse-json: 5.2.0
- pretty-format: 29.7.0
- slash: 3.0.0
- strip-json-comments: 3.1.1
- optionalDependencies:
- '@types/node': 22.19.1
- transitivePeerDependencies:
- - babel-plugin-macros
- - supports-color
-
jest-config@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
'@babel/core': 7.28.5
@@ -34819,7 +34905,38 @@ snapshots:
- babel-plugin-macros
- supports-color
- jest-config@29.7.0(@types/node@24.10.1):
+ jest-config@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
+ dependencies:
+ '@babel/core': 7.28.5
+ '@jest/test-sequencer': 29.7.0
+ '@jest/types': 29.6.3
+ babel-jest: 29.7.0(@babel/core@7.28.5)
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ deepmerge: 4.3.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-get-type: 29.6.3
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-runner: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ '@types/node': 22.19.1
+ ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.9.3)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-config@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
dependencies:
'@babel/core': 7.28.5
'@jest/test-sequencer': 29.7.0
@@ -34845,11 +34962,12 @@ snapshots:
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 24.10.1
+ ts-node: 10.9.2(@types/node@24.10.1)(typescript@5.9.3)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
- jest-config@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
+ jest-config@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
'@babel/core': 7.28.5
'@jest/get-type': 30.1.0
@@ -34876,8 +34994,9 @@ snapshots:
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
- '@types/node': 20.19.25
- esbuild-register: 3.6.0(esbuild@0.27.0)
+ '@types/node': 22.19.1
+ esbuild-register: 3.6.0(esbuild@0.19.12)
+ ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.9.3)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@@ -35526,24 +35645,24 @@ snapshots:
- supports-color
- ts-node
- jest@29.7.0(@types/node@24.10.1):
+ jest@29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 29.7.0
+ '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
'@jest/types': 29.6.3
import-local: 3.2.0
- jest-cli: 29.7.0(@types/node@24.10.1)
+ jest-cli: 29.7.0(@types/node@24.10.1)(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
- supports-color
- ts-node
- jest@30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0)):
+ jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)):
dependencies:
- '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.27.0))
+ '@jest/core': 30.2.0(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
'@jest/types': 30.2.0
import-local: 3.2.0
- jest-cli: 30.2.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.0))
+ jest-cli: 30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.19.12))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@@ -39310,16 +39429,6 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.7
- react-server-dom-webpack@19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.19.12)):
- dependencies:
- acorn-loose: 8.5.2
- neo-async: 2.6.2
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
- webpack: 5.100.2(esbuild@0.19.12)
- webpack-sources: 3.3.3
- optional: true
-
react-server-dom-webpack@19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.100.2(esbuild@0.27.0)):
dependencies:
acorn-loose: 8.5.2
@@ -39329,6 +39438,16 @@ snapshots:
webpack: 5.100.2(esbuild@0.27.0)
webpack-sources: 3.3.3
+ react-server-dom-webpack@19.0.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(webpack@5.97.1(esbuild@0.19.12)):
+ dependencies:
+ acorn-loose: 8.5.2
+ neo-async: 2.6.2
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ webpack: 5.97.1(esbuild@0.19.12)
+ webpack-sources: 3.3.3
+ optional: true
+
react-style-singleton@2.2.3(@types/react@19.2.7)(react@19.1.0):
dependencies:
get-nonce: 1.0.1
@@ -40580,14 +40699,14 @@ snapshots:
ansi-escapes: 4.3.2
supports-hyperlinks: 2.3.0
- terser-webpack-plugin@5.3.14(esbuild@0.19.12)(webpack@5.100.2(esbuild@0.19.12)):
+ terser-webpack-plugin@5.3.14(esbuild@0.19.12)(webpack@5.97.1(esbuild@0.19.12)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
serialize-javascript: 6.0.2
terser: 5.44.1
- webpack: 5.100.2(esbuild@0.19.12)
+ webpack: 5.97.1(esbuild@0.19.12)
optionalDependencies:
esbuild: 0.19.12
@@ -40613,6 +40732,15 @@ snapshots:
optionalDependencies:
esbuild: 0.27.0
+ terser-webpack-plugin@5.3.14(webpack@5.100.2):
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ jest-worker: 27.5.1
+ schema-utils: 4.3.3
+ serialize-javascript: 6.0.2
+ terser: 5.44.1
+ webpack: 5.100.2
+
terser-webpack-plugin@5.3.14(webpack@5.97.1):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@@ -40774,27 +40902,6 @@ snapshots:
ts-interface-checker@0.1.13: {}
- ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.19.12)(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3):
- dependencies:
- bs-logger: 0.2.6
- fast-json-stable-stringify: 2.1.0
- handlebars: 4.7.8
- jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
- json5: 2.2.3
- lodash.memoize: 4.1.2
- make-error: 1.3.6
- semver: 7.7.3
- type-fest: 4.41.0
- typescript: 5.9.3
- yargs-parser: 21.1.1
- optionalDependencies:
- '@babel/core': 7.28.5
- '@jest/transform': 30.2.0
- '@jest/types': 30.2.0
- babel-jest: 30.2.0(@babel/core@7.28.5)
- esbuild: 0.19.12
- jest-util: 30.2.0
-
ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.0)(jest-util@30.2.0)(jest@30.2.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.0))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3):
dependencies:
bs-logger: 0.2.6
@@ -40816,15 +40923,25 @@ snapshots:
esbuild: 0.27.0
jest-util: 30.2.0
- ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.19.12)):
+ ts-jest@29.4.5(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(jest-util@30.2.0)(jest@29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(typescript@5.9.3):
dependencies:
- chalk: 4.1.2
- enhanced-resolve: 5.18.3
- micromatch: 4.0.8
+ bs-logger: 0.2.6
+ fast-json-stable-stringify: 2.1.0
+ handlebars: 4.7.8
+ jest: 29.7.0(@types/node@22.19.1)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
+ json5: 2.2.3
+ lodash.memoize: 4.1.2
+ make-error: 1.3.6
semver: 7.7.3
- source-map: 0.7.6
+ type-fest: 4.41.0
typescript: 5.9.3
- webpack: 5.100.2(esbuild@0.19.12)
+ yargs-parser: 21.1.1
+ optionalDependencies:
+ '@babel/core': 7.28.5
+ '@jest/transform': 30.2.0
+ '@jest/types': 30.2.0
+ babel-jest: 30.2.0(@babel/core@7.28.5)
+ jest-util: 30.2.0
ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2(esbuild@0.27.0)):
dependencies:
@@ -40836,6 +40953,26 @@ snapshots:
typescript: 5.9.3
webpack: 5.100.2(esbuild@0.27.0)
+ ts-loader@9.5.4(typescript@5.9.3)(webpack@5.100.2):
+ dependencies:
+ chalk: 4.1.2
+ enhanced-resolve: 5.18.3
+ micromatch: 4.0.8
+ semver: 7.7.3
+ source-map: 0.7.6
+ typescript: 5.9.3
+ webpack: 5.100.2
+
+ ts-loader@9.5.4(typescript@5.9.3)(webpack@5.97.1(esbuild@0.19.12)):
+ dependencies:
+ chalk: 4.1.2
+ enhanced-resolve: 5.18.3
+ micromatch: 4.0.8
+ semver: 7.7.3
+ source-map: 0.7.6
+ typescript: 5.9.3
+ webpack: 5.97.1(esbuild@0.19.12)
+
ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3):
dependencies:
'@cspotcode/source-map-support': 0.8.1
@@ -41415,6 +41552,23 @@ snapshots:
lightningcss: 1.30.2
terser: 5.44.1
+ vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.53.3
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 20.19.25
+ fsevents: 2.3.3
+ jiti: 1.21.7
+ lightningcss: 1.30.2
+ terser: 5.44.1
+ tsx: 4.20.6
+ yaml: 2.8.1
+
vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1):
dependencies:
esbuild: 0.25.12
@@ -41517,6 +41671,10 @@ snapshots:
tsx: 4.20.6
yaml: 2.8.1
+ vitefu@1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)):
+ optionalDependencies:
+ vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
+
vitefu@1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)):
optionalDependencies:
vite: 6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)(yaml@2.8.1)
@@ -41773,7 +41931,7 @@ snapshots:
webpack-sources@3.3.3: {}
- webpack@5.100.2(esbuild@0.19.12):
+ webpack@5.100.2:
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.8
@@ -41797,7 +41955,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 4.3.3
tapable: 2.3.0
- terser-webpack-plugin: 5.3.14(esbuild@0.19.12)(webpack@5.100.2(esbuild@0.19.12))
+ terser-webpack-plugin: 5.3.14(webpack@5.100.2)
watchpack: 2.4.4
webpack-sources: 3.3.3
transitivePeerDependencies:
@@ -41867,6 +42025,36 @@ snapshots:
- esbuild
- uglify-js
+ webpack@5.97.1(esbuild@0.19.12):
+ dependencies:
+ '@types/eslint-scope': 3.7.7
+ '@types/estree': 1.0.8
+ '@webassemblyjs/ast': 1.14.1
+ '@webassemblyjs/wasm-edit': 1.14.1
+ '@webassemblyjs/wasm-parser': 1.14.1
+ acorn: 8.15.0
+ browserslist: 4.28.0
+ chrome-trace-event: 1.0.4
+ enhanced-resolve: 5.18.3
+ es-module-lexer: 1.7.0
+ eslint-scope: 5.1.1
+ events: 3.3.0
+ glob-to-regexp: 0.4.1
+ graceful-fs: 4.2.11
+ json-parse-even-better-errors: 2.3.1
+ loader-runner: 4.3.1
+ mime-types: 2.1.35
+ neo-async: 2.6.2
+ schema-utils: 3.3.0
+ tapable: 2.3.0
+ terser-webpack-plugin: 5.3.14(esbuild@0.19.12)(webpack@5.97.1(esbuild@0.19.12))
+ watchpack: 2.4.4
+ webpack-sources: 3.3.3
+ transitivePeerDependencies:
+ - '@swc/core'
+ - esbuild
+ - uglify-js
+
webpack@5.97.1(esbuild@0.27.0):
dependencies:
'@types/eslint-scope': 3.7.7
From 48c5cb48f73ea4b17f2d73abbee265e731939fa4 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 16:37:18 +0100
Subject: [PATCH 10/34] =?UTF-8?q?=F0=9F=90=9B=20fix:=20add=20MIDDLEWARE=5F?=
=?UTF-8?q?URL=20to=20manacore-web=20Dockerfile?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add MIDDLEWARE_URL as build argument and environment variable
for SvelteKit's $env/static/private at build time.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/manacore/apps/web/Dockerfile | 2 ++
1 file changed, 2 insertions(+)
diff --git a/apps/manacore/apps/web/Dockerfile b/apps/manacore/apps/web/Dockerfile
index 3310a6326..91418444c 100644
--- a/apps/manacore/apps/web/Dockerfile
+++ b/apps/manacore/apps/web/Dockerfile
@@ -3,9 +3,11 @@ FROM node:20-alpine AS builder
# Build arguments for SvelteKit static env vars
ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001
+ARG MIDDLEWARE_URL=http://mana-core-middleware:3000
# Set as environment variables for build
ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL
+ENV MIDDLEWARE_URL=$MIDDLEWARE_URL
# Install pnpm
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
From ee091c4b1093360500bbefb572b22e41ddb18ad8 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 17:04:35 +0100
Subject: [PATCH 11/34] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20migrate?=
=?UTF-8?q?=20manacore-web=20from=20Supabase=20to=20mana-core-auth?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add password reset functionality to mana-core-auth using Better Auth
- Add forgot-password and reset-password endpoints with DTOs
- Update shared-auth package with resetPassword method and endpoint
- Update manacore-web auth store with resetPassword method
- Refactor reset-password pages to use mana-core-auth instead of Supabase
- Remove Supabase dependencies from manacore-web package.json
- Remove Supabase server code (hooks.server.ts, supabase.ts, API routes)
- Update Dockerfile to remove shared-supabase dependency
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/manacore/apps/web/Dockerfile | 1 -
apps/manacore/apps/web/package.json | 3 -
apps/manacore/apps/web/src/app.d.ts | 23 +-
apps/manacore/apps/web/src/hooks.server.ts | 24 +-
.../apps/web/src/lib/server/supabase.ts | 43 ----
.../apps/web/src/lib/stores/auth.svelte.ts | 23 ++
.../(auth)/forgot-password/+page.server.ts | 39 ----
.../(auth)/reset-password/+page.server.ts | 44 ----
.../routes/(auth)/reset-password/+page.svelte | 178 ++++++---------
.../apps/web/src/routes/+layout.server.ts | 9 +-
apps/manacore/apps/web/src/routes/+layout.ts | 37 +--
.../routes/api/auth/set-session/+server.ts | 36 ---
.../routes/api/auth/verify-token/+server.ts | 33 ---
.../auth/reset-password/+page.server.ts | 44 ----
.../routes/auth/reset-password/+page.svelte | 211 +-----------------
packages/shared-auth/src/core/authService.ts | 36 +++
packages/shared-auth/src/types/index.ts | 1 +
pnpm-lock.yaml | 20 --
.../src/auth/auth.controller.ts | 35 +++
.../src/auth/better-auth.config.ts | 26 ++-
.../src/auth/dto/forgot-password.dto.ts | 22 ++
.../src/auth/dto/reset-password.dto.ts | 22 ++
.../src/auth/services/better-auth.service.ts | 86 +++++++
23 files changed, 357 insertions(+), 639 deletions(-)
delete mode 100644 apps/manacore/apps/web/src/lib/server/supabase.ts
delete mode 100644 apps/manacore/apps/web/src/routes/(auth)/forgot-password/+page.server.ts
delete mode 100644 apps/manacore/apps/web/src/routes/(auth)/reset-password/+page.server.ts
delete mode 100644 apps/manacore/apps/web/src/routes/api/auth/set-session/+server.ts
delete mode 100644 apps/manacore/apps/web/src/routes/api/auth/verify-token/+server.ts
delete mode 100644 apps/manacore/apps/web/src/routes/auth/reset-password/+page.server.ts
create mode 100644 services/mana-core-auth/src/auth/dto/forgot-password.dto.ts
create mode 100644 services/mana-core-auth/src/auth/dto/reset-password.dto.ts
diff --git a/apps/manacore/apps/web/Dockerfile b/apps/manacore/apps/web/Dockerfile
index 91418444c..187511bc5 100644
--- a/apps/manacore/apps/web/Dockerfile
+++ b/apps/manacore/apps/web/Dockerfile
@@ -35,7 +35,6 @@ COPY packages/shared-theme-ui ./packages/shared-theme-ui
COPY packages/shared-subscription-types ./packages/shared-subscription-types
COPY packages/shared-subscription-ui ./packages/shared-subscription-ui
COPY packages/shared-profile-ui ./packages/shared-profile-ui
-COPY packages/shared-supabase ./packages/shared-supabase
COPY packages/shared-types ./packages/shared-types
COPY packages/shared-ui ./packages/shared-ui
COPY packages/shared-utils ./packages/shared-utils
diff --git a/apps/manacore/apps/web/package.json b/apps/manacore/apps/web/package.json
index 8c019c6dc..81222619d 100644
--- a/apps/manacore/apps/web/package.json
+++ b/apps/manacore/apps/web/package.json
@@ -49,15 +49,12 @@
"@manacore/shared-profile-ui": "workspace:*",
"@manacore/shared-subscription-types": "workspace:*",
"@manacore/shared-subscription-ui": "workspace:*",
- "@manacore/shared-supabase": "workspace:*",
"@manacore/shared-tailwind": "workspace:*",
"@manacore/shared-theme": "workspace:*",
"@manacore/shared-theme-ui": "workspace:*",
"@manacore/shared-types": "workspace:*",
"@manacore/shared-ui": "workspace:*",
"@manacore/shared-utils": "workspace:*",
- "@supabase/ssr": "^0.5.2",
- "@supabase/supabase-js": "^2.81.1",
"svelte-dnd-action": "^0.9.68",
"svelte-i18n": "^4.0.0"
},
diff --git a/apps/manacore/apps/web/src/app.d.ts b/apps/manacore/apps/web/src/app.d.ts
index 9272c6861..1476def65 100644
--- a/apps/manacore/apps/web/src/app.d.ts
+++ b/apps/manacore/apps/web/src/app.d.ts
@@ -1,18 +1,15 @@
-import type { Session, SupabaseClient, User } from '@supabase/supabase-js';
-
+/**
+ * App type declarations for ManaCore web app
+ *
+ * Authentication is handled entirely by Mana Core Auth (@manacore/shared-auth).
+ * No Supabase is needed - all data comes from mana-core-auth APIs.
+ */
declare global {
namespace App {
- interface Locals {
- supabase: SupabaseClient;
- safeGetSession: () => Promise<{ session: Session | null; user: User | null }>;
- session: Session | null;
- user: User | null;
- }
- interface PageData {
- // Auth is handled by Mana Core Auth (@manacore/shared-auth), not Supabase
- // Supabase is used for database operations only
- supabase?: SupabaseClient;
- }
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+ interface Locals {}
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
+ interface PageData {}
// interface Error {}
// interface Platform {}
}
diff --git a/apps/manacore/apps/web/src/hooks.server.ts b/apps/manacore/apps/web/src/hooks.server.ts
index 1ae4f5d6a..446ce160c 100644
--- a/apps/manacore/apps/web/src/hooks.server.ts
+++ b/apps/manacore/apps/web/src/hooks.server.ts
@@ -1,29 +1,11 @@
-import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
-import { createServerClient } from '@supabase/ssr';
import type { Handle } from '@sveltejs/kit';
/**
* Server hooks for ManaCore web app
*
- * Note: Authentication is handled client-side via Mana Core Auth.
- * Supabase is only used for database operations (not auth).
+ * Authentication is handled entirely by Mana Core Auth (@manacore/shared-auth).
+ * No Supabase is needed - all data comes from mana-core-auth APIs.
*/
export const handle: Handle = async ({ event, resolve }) => {
- // Create Supabase client for database operations only
- event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
- cookies: {
- getAll: () => event.cookies.getAll(),
- setAll: (cookiesToSet) => {
- cookiesToSet.forEach(({ name, value, options }) => {
- event.cookies.set(name, value, { ...options, path: '/' });
- });
- },
- },
- }) as any;
-
- return resolve(event, {
- filterSerializedResponseHeaders(name) {
- return name === 'content-range' || name === 'x-supabase-api-version';
- },
- });
+ return resolve(event);
};
diff --git a/apps/manacore/apps/web/src/lib/server/supabase.ts b/apps/manacore/apps/web/src/lib/server/supabase.ts
deleted file mode 100644
index 31f4b14a4..000000000
--- a/apps/manacore/apps/web/src/lib/server/supabase.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import type { RequestEvent } from '@sveltejs/kit';
-
-export async function getUser(event: RequestEvent) {
- const {
- data: { user },
- error,
- } = await event.locals.supabase.auth.getUser();
-
- if (error) {
- console.error('Error fetching user:', error);
- return null;
- }
-
- return user;
-}
-
-export async function getSession(event: RequestEvent) {
- const {
- data: { session },
- error,
- } = await event.locals.supabase.auth.getSession();
-
- if (error) {
- console.error('Error fetching session:', error);
- return null;
- }
-
- return session;
-}
-
-export async function requireAuth(event: RequestEvent) {
- const session = await getSession(event);
-
- if (!session) {
- throw new Error('Unauthorized');
- }
-
- return session;
-}
-
-export function getSupabaseServerClient(event: RequestEvent) {
- return event.locals.supabase;
-}
diff --git a/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts b/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
index 12cb7b306..5b6edc77a 100644
--- a/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
@@ -183,6 +183,29 @@ export const authStore = {
}
},
+ /**
+ * Reset password with token
+ */
+ async resetPassword(token: string, newPassword: string) {
+ const authService = getAuthService();
+ if (!authService) {
+ return { success: false, error: 'Auth not available on server' };
+ }
+
+ try {
+ const result = await authService.resetPassword(token, newPassword);
+
+ if (!result.success) {
+ return { success: false, error: result.error || 'Password reset failed' };
+ }
+
+ return { success: true };
+ } catch (error) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ return { success: false, error: errorMessage };
+ }
+ },
+
/**
* Get access token for API calls
*/
diff --git a/apps/manacore/apps/web/src/routes/(auth)/forgot-password/+page.server.ts b/apps/manacore/apps/web/src/routes/(auth)/forgot-password/+page.server.ts
deleted file mode 100644
index ecad6ad9a..000000000
--- a/apps/manacore/apps/web/src/routes/(auth)/forgot-password/+page.server.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { fail } from '@sveltejs/kit';
-import type { Actions } from './$types';
-
-export const actions: Actions = {
- default: async ({ request, locals: { supabase }, url }) => {
- const formData = await request.formData();
- const email = formData.get('email') as string;
-
- if (!email) {
- return fail(400, {
- error: 'Email is required',
- email,
- });
- }
-
- // Get the origin for the redirect URL
- const origin = url.origin;
- const redirectTo = `${origin}/auth/reset-password`;
-
- // Send password reset email
- const { error } = await supabase.auth.resetPasswordForEmail(email, {
- redirectTo,
- });
-
- if (error) {
- console.error('Password reset error:', error);
- return fail(400, {
- error: error.message,
- email,
- });
- }
-
- // Return success (we don't reveal if the email exists for security)
- return {
- success: true,
- email,
- };
- },
-};
diff --git a/apps/manacore/apps/web/src/routes/(auth)/reset-password/+page.server.ts b/apps/manacore/apps/web/src/routes/(auth)/reset-password/+page.server.ts
deleted file mode 100644
index 70b0f8002..000000000
--- a/apps/manacore/apps/web/src/routes/(auth)/reset-password/+page.server.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { redirect, fail } from '@sveltejs/kit';
-import type { Actions } from './$types';
-
-export const actions: Actions = {
- default: async ({ request, locals: { supabase } }) => {
- const formData = await request.formData();
- const password = formData.get('password') as string;
- const confirmPassword = formData.get('confirmPassword') as string;
-
- // Validate inputs
- if (!password || !confirmPassword) {
- return fail(400, {
- error: 'Both password fields are required',
- });
- }
-
- if (password.length < 6) {
- return fail(400, {
- error: 'Password must be at least 6 characters long',
- });
- }
-
- if (password !== confirmPassword) {
- return fail(400, {
- error: 'Passwords do not match',
- });
- }
-
- // Update the user's password
- const { error } = await supabase.auth.updateUser({
- password,
- });
-
- if (error) {
- console.error('Password update error:', error);
- return fail(400, {
- error: error.message,
- });
- }
-
- // Success - redirect to dashboard
- throw redirect(303, '/dashboard');
- },
-};
diff --git a/apps/manacore/apps/web/src/routes/(auth)/reset-password/+page.svelte b/apps/manacore/apps/web/src/routes/(auth)/reset-password/+page.svelte
index c067b9fc1..c2885f7c7 100644
--- a/apps/manacore/apps/web/src/routes/(auth)/reset-password/+page.svelte
+++ b/apps/manacore/apps/web/src/routes/(auth)/reset-password/+page.svelte
@@ -1,141 +1,103 @@
Reset Password
- {#if verifying}
- Verifying your reset link...
+ {#if success}
+ Password reset successfully
{:else if hasToken}
Enter your new password
{:else}
- Token missing or expired
+ Invalid or missing reset token
{/if}
- {#if verifying}
+ {#if success}
-
-
Verifying your password reset link...
-
-
- {:else if verificationError}
-
-
-
⚠️
+
✅
- {verificationError}
+ Your password has been reset successfully. You will be redirected to the login page
+ shortly.
- Request a new reset link
+ Go to login
{:else if hasToken}
-
@@ -173,9 +136,10 @@
name="confirmPassword"
id="confirmPassword"
autocomplete="new-password"
- placeholder="••••••••"
+ placeholder="Confirm new password"
required
- minlength={6}
+ minlength={12}
+ bind:value={confirmPassword}
/>
@@ -195,10 +159,10 @@
This password reset link is invalid or has expired.
- Back to login
+ Request a new reset link
diff --git a/apps/manacore/apps/web/src/routes/+layout.server.ts b/apps/manacore/apps/web/src/routes/+layout.server.ts
index 11770fd80..907b4b2a0 100644
--- a/apps/manacore/apps/web/src/routes/+layout.server.ts
+++ b/apps/manacore/apps/web/src/routes/+layout.server.ts
@@ -1,7 +1,8 @@
import type { LayoutServerLoad } from './$types';
-export const load: LayoutServerLoad = async ({ cookies }) => {
- return {
- cookies: cookies.getAll(),
- };
+/**
+ * Server layout load - minimal, auth handled by mana-core-auth client-side
+ */
+export const load: LayoutServerLoad = async () => {
+ return {};
};
diff --git a/apps/manacore/apps/web/src/routes/+layout.ts b/apps/manacore/apps/web/src/routes/+layout.ts
index b238de6e1..d33856fe5 100644
--- a/apps/manacore/apps/web/src/routes/+layout.ts
+++ b/apps/manacore/apps/web/src/routes/+layout.ts
@@ -1,35 +1,14 @@
import { waitLocale } from '$lib/i18n';
import '$lib/i18n'; // This triggers the init() call at module scope
-import { createBrowserClient } from '@supabase/ssr';
-import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
import type { LayoutLoad } from './$types';
-export const load: LayoutLoad = async ({ data, depends }) => {
+/**
+ * Layout load function
+ *
+ * Auth is handled entirely by Mana Core Auth (@manacore/shared-auth).
+ * No Supabase is needed - all data comes from mana-core-auth APIs.
+ */
+export const load: LayoutLoad = async () => {
await waitLocale();
-
- /**
- * Declare a dependency so the layout will be invalidated when `invalidate('supabase:auth')` is called.
- */
- depends('supabase:auth');
-
- // Create Supabase client for database operations only
- // Auth is handled by Mana Core Auth (@manacore/shared-auth)
- const supabase = createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
- global: {
- fetch,
- },
- cookies: {
- getAll() {
- return data.cookies;
- },
- setAll(cookiesToSet) {
- // Browser client handles cookies automatically through the browser
- // This is a no-op as cookies are managed via document.cookie in the browser
- },
- },
- });
-
- // Note: Auth session is managed by Mana Core Auth via authStore,
- // not Supabase auth. Supabase is used for database operations only.
- return { supabase };
+ return {};
};
diff --git a/apps/manacore/apps/web/src/routes/api/auth/set-session/+server.ts b/apps/manacore/apps/web/src/routes/api/auth/set-session/+server.ts
deleted file mode 100644
index a03687c6f..000000000
--- a/apps/manacore/apps/web/src/routes/api/auth/set-session/+server.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { json } from '@sveltejs/kit';
-import type { RequestHandler } from './$types';
-
-export const POST: RequestHandler = async ({ request, locals: { supabase } }) => {
- try {
- const { access_token, refresh_token } = await request.json();
-
- if (!access_token || !refresh_token) {
- return json(
- { success: false, error: 'Access token and refresh token are required' },
- { status: 400 }
- );
- }
-
- // Set the session using the tokens from the URL hash
- const { data, error } = await supabase.auth.setSession({
- access_token,
- refresh_token,
- });
-
- if (error) {
- console.error('Set session error:', error);
- return json({ success: false, error: error.message }, { status: 400 });
- }
-
- if (!data.session) {
- return json({ success: false, error: 'Failed to create session' }, { status: 400 });
- }
-
- // Session is now set via cookies by the Supabase client
- return json({ success: true });
- } catch (error) {
- console.error('Unexpected error in set session:', error);
- return json({ success: false, error: 'An unexpected error occurred' }, { status: 500 });
- }
-};
diff --git a/apps/manacore/apps/web/src/routes/api/auth/verify-token/+server.ts b/apps/manacore/apps/web/src/routes/api/auth/verify-token/+server.ts
deleted file mode 100644
index a780638fe..000000000
--- a/apps/manacore/apps/web/src/routes/api/auth/verify-token/+server.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { json } from '@sveltejs/kit';
-import type { RequestHandler } from './$types';
-
-export const POST: RequestHandler = async ({ request, locals: { supabase } }) => {
- try {
- const { token, type } = await request.json();
-
- if (!token || type !== 'recovery') {
- return json({ success: false, error: 'Invalid token or type' }, { status: 400 });
- }
-
- // Verify the OTP token and create a session
- const { data, error } = await supabase.auth.verifyOtp({
- token_hash: token,
- type: 'recovery',
- });
-
- if (error) {
- console.error('Token verification error:', error);
- return json({ success: false, error: error.message }, { status: 400 });
- }
-
- if (!data.session) {
- return json({ success: false, error: 'Failed to create session' }, { status: 400 });
- }
-
- // Session is now set via cookies by the Supabase client
- return json({ success: true });
- } catch (error) {
- console.error('Unexpected error in token verification:', error);
- return json({ success: false, error: 'An unexpected error occurred' }, { status: 500 });
- }
-};
diff --git a/apps/manacore/apps/web/src/routes/auth/reset-password/+page.server.ts b/apps/manacore/apps/web/src/routes/auth/reset-password/+page.server.ts
deleted file mode 100644
index 70b0f8002..000000000
--- a/apps/manacore/apps/web/src/routes/auth/reset-password/+page.server.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { redirect, fail } from '@sveltejs/kit';
-import type { Actions } from './$types';
-
-export const actions: Actions = {
- default: async ({ request, locals: { supabase } }) => {
- const formData = await request.formData();
- const password = formData.get('password') as string;
- const confirmPassword = formData.get('confirmPassword') as string;
-
- // Validate inputs
- if (!password || !confirmPassword) {
- return fail(400, {
- error: 'Both password fields are required',
- });
- }
-
- if (password.length < 6) {
- return fail(400, {
- error: 'Password must be at least 6 characters long',
- });
- }
-
- if (password !== confirmPassword) {
- return fail(400, {
- error: 'Passwords do not match',
- });
- }
-
- // Update the user's password
- const { error } = await supabase.auth.updateUser({
- password,
- });
-
- if (error) {
- console.error('Password update error:', error);
- return fail(400, {
- error: error.message,
- });
- }
-
- // Success - redirect to dashboard
- throw redirect(303, '/dashboard');
- },
-};
diff --git a/apps/manacore/apps/web/src/routes/auth/reset-password/+page.svelte b/apps/manacore/apps/web/src/routes/auth/reset-password/+page.svelte
index abaff7f14..ff15f6e4c 100644
--- a/apps/manacore/apps/web/src/routes/auth/reset-password/+page.svelte
+++ b/apps/manacore/apps/web/src/routes/auth/reset-password/+page.svelte
@@ -1,214 +1,23 @@
-
- Reset Password - ManaCore
-
-
-
-
-
Reset Password
-
- {#if verifying}
- Verifying your reset link...
- {:else if hasToken}
- Enter your new password
- {:else}
- Token missing or expired
- {/if}
-
-
-
- {#if verifying}
-
-
-
-
Verifying your password reset link...
-
-
- {:else if verificationError}
-
-
-
⚠️
-
- {verificationError}
-
-
- Request a new reset link
-
-
-
- {:else if hasToken}
-
-
-
- {:else}
-
-
-
⚠️
-
- This password reset link is invalid or has expired.
-
-
- Back to login
-
-
-
- {/if}
+
diff --git a/packages/shared-auth/src/core/authService.ts b/packages/shared-auth/src/core/authService.ts
index 3caaf4bcd..0d42ecfb0 100644
--- a/packages/shared-auth/src/core/authService.ts
+++ b/packages/shared-auth/src/core/authService.ts
@@ -39,6 +39,7 @@ const DEFAULT_ENDPOINTS: AuthEndpoints = {
refresh: '/api/v1/auth/refresh',
validate: '/api/v1/auth/validate',
forgotPassword: '/api/v1/auth/forgot-password',
+ resetPassword: '/api/v1/auth/reset-password',
googleSignIn: '/api/v1/auth/google-signin',
appleSignIn: '/api/v1/auth/apple-signin',
credits: '/api/v1/credits/balance',
@@ -192,6 +193,41 @@ export function createAuthService(config: AuthServiceConfig) {
}
},
+ /**
+ * Reset password with token
+ */
+ async resetPassword(token: string, newPassword: string): Promise
{
+ try {
+ const response = await fetch(`${baseUrl}${endpoints.resetPassword}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ token, newPassword }),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+
+ if (errorData.message?.includes('expired')) {
+ return { success: false, error: 'Reset link has expired. Please request a new one.' };
+ }
+
+ if (errorData.message?.includes('invalid')) {
+ return { success: false, error: 'Invalid reset link. Please request a new one.' };
+ }
+
+ return { success: false, error: errorData.message || 'Password reset failed' };
+ }
+
+ return { success: true };
+ } catch (error) {
+ console.error('Error resetting password:', error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : 'Unknown error during password reset',
+ };
+ }
+ },
+
/**
* Refresh the authentication tokens
*/
diff --git a/packages/shared-auth/src/types/index.ts b/packages/shared-auth/src/types/index.ts
index 2308bdc24..32d233bb0 100644
--- a/packages/shared-auth/src/types/index.ts
+++ b/packages/shared-auth/src/types/index.ts
@@ -128,6 +128,7 @@ export interface AuthEndpoints {
refresh: string;
validate: string;
forgotPassword: string;
+ resetPassword: string;
googleSignIn: string;
appleSignIn: string;
credits: string;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f21357ea4..4a38c9847 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1442,9 +1442,6 @@ importers:
'@manacore/shared-subscription-ui':
specifier: workspace:*
version: link:../../../../packages/shared-subscription-ui
- '@manacore/shared-supabase':
- specifier: workspace:*
- version: link:../../../../packages/shared-supabase
'@manacore/shared-tailwind':
specifier: workspace:*
version: link:../../../../packages/shared-tailwind
@@ -1463,12 +1460,6 @@ importers:
'@manacore/shared-utils':
specifier: workspace:*
version: link:../../../../packages/shared-utils
- '@supabase/ssr':
- specifier: ^0.5.2
- version: 0.5.2(@supabase/supabase-js@2.84.0)
- '@supabase/supabase-js':
- specifier: ^2.81.1
- version: 2.84.0
svelte-dnd-action:
specifier: ^0.9.68
version: 0.9.68(svelte@5.44.0)
@@ -9042,11 +9033,6 @@ packages:
resolution: {integrity: sha512-ThqjxiCwWiZAroHnYPmnNl6tZk6jxGcG2a7Hp/3kcolPcMj89kWjUTA3cHmhdIWYsP84fHp8MAQjYWMLf7HEUg==}
engines: {node: '>=20.0.0'}
- '@supabase/ssr@0.5.2':
- resolution: {integrity: sha512-n3plRhr2Bs8Xun1o4S3k1CDv17iH5QY9YcoEvXX3bxV1/5XSasA0mNXYycFmADIdtdE6BG9MRjP5CGIs8qxC8A==}
- peerDependencies:
- '@supabase/supabase-js': ^2.43.4
-
'@supabase/ssr@0.7.0':
resolution: {integrity: sha512-G65t5EhLSJ5c8hTCcXifSL9Q/ZRXvqgXeNo+d3P56f4U1IxwTqjB64UfmfixvmMcjuxnq2yGqEWVJqUcO+AzAg==}
peerDependencies:
@@ -26238,12 +26224,6 @@ snapshots:
- bufferutil
- utf-8-validate
- '@supabase/ssr@0.5.2(@supabase/supabase-js@2.84.0)':
- dependencies:
- '@supabase/supabase-js': 2.84.0
- '@types/cookie': 0.6.0
- cookie: 0.7.2
-
'@supabase/ssr@0.7.0(@supabase/supabase-js@2.84.0)':
dependencies:
'@supabase/supabase-js': 2.84.0
diff --git a/services/mana-core-auth/src/auth/auth.controller.ts b/services/mana-core-auth/src/auth/auth.controller.ts
index 980ff9301..5ad646960 100644
--- a/services/mana-core-auth/src/auth/auth.controller.ts
+++ b/services/mana-core-auth/src/auth/auth.controller.ts
@@ -18,6 +18,8 @@ import { RegisterB2BDto } from './dto/register-b2b.dto';
import { InviteEmployeeDto } from './dto/invite-employee.dto';
import { AcceptInvitationDto } from './dto/accept-invitation.dto';
import { SetActiveOrganizationDto } from './dto/set-active-organization.dto';
+import { ForgotPasswordDto } from './dto/forgot-password.dto';
+import { ResetPasswordDto } from './dto/reset-password.dto';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
/**
@@ -137,6 +139,39 @@ export class AuthController {
return this.betterAuthService.getJwks();
}
+ // =========================================================================
+ // Password Reset Endpoints
+ // =========================================================================
+
+ /**
+ * Request password reset
+ *
+ * Initiates the password reset flow by sending an email with a reset link.
+ * Always returns success to prevent email enumeration attacks.
+ */
+ @Post('forgot-password')
+ @HttpCode(HttpStatus.OK)
+ async forgotPassword(@Body() forgotPasswordDto: ForgotPasswordDto) {
+ return this.betterAuthService.requestPasswordReset(
+ forgotPasswordDto.email,
+ forgotPasswordDto.redirectTo
+ );
+ }
+
+ /**
+ * Reset password with token
+ *
+ * Completes the password reset using the token from the email link.
+ */
+ @Post('reset-password')
+ @HttpCode(HttpStatus.OK)
+ async resetPassword(@Body() resetPasswordDto: ResetPasswordDto) {
+ return this.betterAuthService.resetPassword(
+ resetPasswordDto.token,
+ resetPasswordDto.newPassword
+ );
+ }
+
// =========================================================================
// B2B Registration
// =========================================================================
diff --git a/services/mana-core-auth/src/auth/better-auth.config.ts b/services/mana-core-auth/src/auth/better-auth.config.ts
index 0f0018824..13efe856f 100644
--- a/services/mana-core-auth/src/auth/better-auth.config.ts
+++ b/services/mana-core-auth/src/auth/better-auth.config.ts
@@ -80,12 +80,36 @@ export function createBetterAuth(databaseUrl: string) {
},
}),
- // Email/password authentication only
+ // Email/password authentication with password reset
emailAndPassword: {
enabled: true,
requireEmailVerification: false, // Can enable later
minPasswordLength: 12,
maxPasswordLength: 128,
+
+ /**
+ * Password Reset Configuration
+ *
+ * Better Auth provides password reset via:
+ * - auth.api.forgetPassword({ email }) - Sends reset email
+ * - auth.api.resetPassword({ newPassword, token }) - Resets password
+ *
+ * @see https://www.better-auth.com/docs/authentication/email-password#password-reset
+ */
+ sendResetPassword: async ({ user, url, token }) => {
+ // TODO: Implement email sending service (e.g., Resend, SendGrid)
+ // For now, log the reset URL for development
+ console.log('[Password Reset] User:', user.email);
+ console.log('[Password Reset] Reset URL:', url);
+ console.log('[Password Reset] Token:', token);
+
+ // In production, send an email like:
+ // await sendEmail({
+ // to: user.email,
+ // subject: 'Reset your password',
+ // html: `Reset your password`
+ // });
+ },
},
// Session configuration
diff --git a/services/mana-core-auth/src/auth/dto/forgot-password.dto.ts b/services/mana-core-auth/src/auth/dto/forgot-password.dto.ts
new file mode 100644
index 000000000..22a1c8bb8
--- /dev/null
+++ b/services/mana-core-auth/src/auth/dto/forgot-password.dto.ts
@@ -0,0 +1,22 @@
+import { IsEmail, IsOptional, IsString, IsUrl } from 'class-validator';
+
+/**
+ * Forgot Password DTO
+ *
+ * Request body for initiating password reset.
+ */
+export class ForgotPasswordDto {
+ /**
+ * User's email address
+ */
+ @IsEmail()
+ email: string;
+
+ /**
+ * Optional redirect URL after password reset
+ * The reset token will be appended as a query parameter
+ */
+ @IsOptional()
+ @IsString()
+ redirectTo?: string;
+}
diff --git a/services/mana-core-auth/src/auth/dto/reset-password.dto.ts b/services/mana-core-auth/src/auth/dto/reset-password.dto.ts
new file mode 100644
index 000000000..fa4d2367b
--- /dev/null
+++ b/services/mana-core-auth/src/auth/dto/reset-password.dto.ts
@@ -0,0 +1,22 @@
+import { IsString, MinLength, MaxLength } from 'class-validator';
+
+/**
+ * Reset Password DTO
+ *
+ * Request body for resetting password with token.
+ */
+export class ResetPasswordDto {
+ /**
+ * Reset token from email link
+ */
+ @IsString()
+ token: string;
+
+ /**
+ * New password (must meet password requirements)
+ */
+ @IsString()
+ @MinLength(12, { message: 'Password must be at least 12 characters long' })
+ @MaxLength(128, { message: 'Password must be at most 128 characters long' })
+ newPassword: string;
+}
diff --git a/services/mana-core-auth/src/auth/services/better-auth.service.ts b/services/mana-core-auth/src/auth/services/better-auth.service.ts
index 11cfce791..25a1be0fc 100644
--- a/services/mana-core-auth/src/auth/services/better-auth.service.ts
+++ b/services/mana-core-auth/src/auth/services/better-auth.service.ts
@@ -845,6 +845,92 @@ export class BetterAuthService {
}
}
+ // =========================================================================
+ // Password Reset Methods
+ // =========================================================================
+
+ /**
+ * Request password reset
+ *
+ * Sends a password reset email to the user.
+ * Uses Better Auth's forgetPassword API.
+ *
+ * @param email - User's email address
+ * @param redirectTo - Optional URL to redirect after reset (used in email link)
+ * @returns Success status
+ */
+ async requestPasswordReset(
+ email: string,
+ redirectTo?: string
+ ): Promise<{ success: boolean; message: string }> {
+ try {
+ // Better Auth's forgetPassword method
+ // See: https://www.better-auth.com/docs/authentication/email-password#password-reset
+ await (this.auth.api as any).forgetPassword({
+ body: {
+ email,
+ redirectTo,
+ },
+ });
+
+ // Always return success to prevent email enumeration
+ return {
+ success: true,
+ message: 'If an account with that email exists, a password reset link has been sent',
+ };
+ } catch (error) {
+ console.error('[requestPasswordReset] Error:', error);
+ // Always return success to prevent email enumeration attacks
+ return {
+ success: true,
+ message: 'If an account with that email exists, a password reset link has been sent',
+ };
+ }
+ }
+
+ /**
+ * Reset password with token
+ *
+ * Resets the user's password using the token from the reset email.
+ * Uses Better Auth's resetPassword API.
+ *
+ * @param token - Reset token from email link
+ * @param newPassword - New password to set
+ * @returns Success status
+ * @throws UnauthorizedException if token is invalid or expired
+ */
+ async resetPassword(
+ token: string,
+ newPassword: string
+ ): Promise<{ success: boolean; message: string }> {
+ try {
+ // Better Auth's resetPassword method
+ // See: https://www.better-auth.com/docs/authentication/email-password#password-reset
+ await (this.auth.api as any).resetPassword({
+ body: {
+ token,
+ newPassword,
+ },
+ });
+
+ return {
+ success: true,
+ message: 'Password has been reset successfully',
+ };
+ } catch (error: unknown) {
+ if (error instanceof Error) {
+ if (
+ error.message?.includes('invalid') ||
+ error.message?.includes('expired') ||
+ error.message?.includes('not found')
+ ) {
+ throw new UnauthorizedException('Invalid or expired reset token');
+ }
+ }
+ throw error;
+ }
+ }
+
/**
* Get JWKS (JSON Web Key Set)
*
From 9746db1476882941a546a5b34d5c7bdc6b58d438 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 17:35:11 +0100
Subject: [PATCH 12/34] =?UTF-8?q?=F0=9F=9A=80=20ci:=20add=20manacore,=20to?=
=?UTF-8?q?do,=20calendar,=20clock=20to=20tagged=20deployment=20workflow?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Added 4 new projects to workflow_dispatch options
- Configured PROJECT_APPS mappings (manacore: web only, others: backend+web)
- Set proper ports: calendar=3014, clock=3017, todo=3018, web apps have distinct ports
- Handle custom Dockerfiles for apps with shared package dependencies
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
.github/workflows/cd-staging-tagged.yml | 39 ++++++++++++++++++++++---
1 file changed, 35 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/cd-staging-tagged.yml b/.github/workflows/cd-staging-tagged.yml
index 72a267e8a..542bc7700 100644
--- a/.github/workflows/cd-staging-tagged.yml
+++ b/.github/workflows/cd-staging-tagged.yml
@@ -21,6 +21,10 @@ on:
- zitare
- presi
- mana-core-auth
+ - manacore
+ - todo
+ - calendar
+ - clock
apps:
description: 'Apps to deploy (comma-separated: backend,web,landing or "all")'
required: true
@@ -104,6 +108,10 @@ jobs:
PROJECT_APPS[zitare]="backend,web"
PROJECT_APPS[presi]="backend,web"
PROJECT_APPS[mana-core-auth]="service"
+ PROJECT_APPS[manacore]="web"
+ PROJECT_APPS[todo]="backend,web"
+ PROJECT_APPS[calendar]="backend,web"
+ PROJECT_APPS[clock]="backend,web"
# Expand "all" to available apps
if [ "$APPS" == "all" ]; then
@@ -135,8 +143,17 @@ jobs:
IMAGE_NAME="${PROJECT}-backend"
;;
web)
- DOCKERFILE_PATH="docker/templates/Dockerfile.sveltekit"
- CONTEXT_PATH="apps/$PROJECT/apps/web"
+ # Apps with their own Dockerfiles (need monorepo root for shared packages)
+ case "$PROJECT" in
+ manacore|todo|calendar|clock)
+ DOCKERFILE_PATH="apps/$PROJECT/apps/web/Dockerfile"
+ CONTEXT_PATH="."
+ ;;
+ *)
+ DOCKERFILE_PATH="docker/templates/Dockerfile.sveltekit"
+ CONTEXT_PATH="apps/$PROJECT/apps/web"
+ ;;
+ esac
IMAGE_NAME="${PROJECT}-web"
;;
landing)
@@ -146,15 +163,29 @@ jobs:
;;
esac
- # Set ports per project
+ # Set backend ports per project
case "$PROJECT" in
chat) PORT="3002" ;;
picture) PORT="3006" ;;
manadeck) PORT="3009" ;;
zitare) PORT="3007" ;;
presi) PORT="3008" ;;
+ calendar) PORT="3014" ;;
+ clock) PORT="3017" ;;
+ todo) PORT="3018" ;;
esac
- HEALTH_PATH="/api/health"
+
+ # Override ports for web apps (SvelteKit uses different ports)
+ if [ "$APP" == "web" ]; then
+ case "$PROJECT" in
+ manacore) PORT="5173" ;;
+ todo) PORT="5188" ;;
+ calendar) PORT="5185" ;;
+ clock) PORT="5187" ;;
+ *) PORT="5173" ;; # default SvelteKit port
+ esac
+ fi
+ HEALTH_PATH="/health"
;;
esac
From 59ce92af1af222b900495e96b6909a8443d98c31 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 17:54:40 +0100
Subject: [PATCH 13/34] =?UTF-8?q?=F0=9F=94=A7=20fix:=20deployment=20workfl?=
=?UTF-8?q?ow=20-=20lowercase=20image=20prefix,=20service=20names,=20and?=
=?UTF-8?q?=20port=20fixes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Fix Docker image prefix to lowercase (memo-2023) for Docker compatibility
- Keep service names with hyphens to match docker-compose.staging.yml
- Add step to sync docker-compose.staging.yml to server before deploy
- Fix calendar port to 3016/5186 to match staging compose
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
.github/workflows/cd-staging-tagged.yml | 22 +++++++++++++++-------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/cd-staging-tagged.yml b/.github/workflows/cd-staging-tagged.yml
index 542bc7700..612c53e22 100644
--- a/.github/workflows/cd-staging-tagged.yml
+++ b/.github/workflows/cd-staging-tagged.yml
@@ -40,7 +40,8 @@ env:
NODE_VERSION: '20'
PNPM_VERSION: '9.15.0'
REGISTRY: ghcr.io
- IMAGE_PREFIX: ghcr.io/${{ github.repository_owner }}
+ # Note: repository_owner is lowercased for Docker compatibility
+ IMAGE_PREFIX: ghcr.io/memo-2023
jobs:
# Parse tag or inputs to determine what to deploy
@@ -163,14 +164,14 @@ jobs:
;;
esac
- # Set backend ports per project
+ # Set backend ports per project (must match docker-compose.staging.yml)
case "$PROJECT" in
chat) PORT="3002" ;;
picture) PORT="3006" ;;
manadeck) PORT="3009" ;;
zitare) PORT="3007" ;;
presi) PORT="3008" ;;
- calendar) PORT="3014" ;;
+ calendar) PORT="3016" ;;
clock) PORT="3017" ;;
todo) PORT="3018" ;;
esac
@@ -180,7 +181,7 @@ jobs:
case "$PROJECT" in
manacore) PORT="5173" ;;
todo) PORT="5188" ;;
- calendar) PORT="5185" ;;
+ calendar) PORT="5186" ;;
clock) PORT="5187" ;;
*) PORT="5173" ;; # default SvelteKit port
esac
@@ -298,6 +299,13 @@ jobs:
mkdir -p ~/.ssh
ssh-keyscan -H ${{ secrets.STAGING_HOST }} >> ~/.ssh/known_hosts
+ - name: Sync docker-compose to staging
+ run: |
+ # Ensure staging directory exists
+ ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} "mkdir -p ~/manacore-staging"
+ # Copy the docker-compose file
+ scp docker-compose.staging.yml ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }}:~/manacore-staging/docker-compose.yml
+
- name: Deploy service
env:
VERSION: ${{ needs.parse-deployment.outputs.version }}
@@ -311,10 +319,10 @@ jobs:
# Pull the new image
docker pull ${{ env.IMAGE_PREFIX }}/$IMAGE_NAME:$VERSION
- # Restart only the specific service
- SERVICE_NAME=\$(echo "$IMAGE_NAME" | tr '-' '_')
+ # Service name matches docker-compose service name (with hyphens)
+ SERVICE_NAME="$IMAGE_NAME"
- if docker compose ps | grep -q "\$SERVICE_NAME"; then
+ if docker compose ps -a | grep -q "$IMAGE_NAME"; then
echo "Updating existing service: \$SERVICE_NAME"
docker compose pull \$SERVICE_NAME || true
docker compose up -d --no-deps --force-recreate \$SERVICE_NAME
From 73dfe57664b48edc4b40a47f7cc178f7828c6436 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 18:13:17 +0100
Subject: [PATCH 14/34] =?UTF-8?q?=F0=9F=94=A7=20fix:=20add=20GHCR=20authen?=
=?UTF-8?q?tication=20for=20staging=20server?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The staging server needs to authenticate to ghcr.io to pull private images.
Added docker login step using GHCR_PAT secret before deployment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
.github/workflows/cd-staging-tagged.yml | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/.github/workflows/cd-staging-tagged.yml b/.github/workflows/cd-staging-tagged.yml
index 612c53e22..525a6f023 100644
--- a/.github/workflows/cd-staging-tagged.yml
+++ b/.github/workflows/cd-staging-tagged.yml
@@ -306,6 +306,12 @@ jobs:
# Copy the docker-compose file
scp docker-compose.staging.yml ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }}:~/manacore-staging/docker-compose.yml
+ - name: Login to GHCR on staging server
+ run: |
+ ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
+ echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
+ EOF
+
- name: Deploy service
env:
VERSION: ${{ needs.parse-deployment.outputs.version }}
From 3485bf08739b3b065e31085ec2a4e5911df7acfc Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 18:16:11 +0100
Subject: [PATCH 15/34] fix(ci): use GITHUB_TOKEN for GHCR auth on staging
server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Match the working pattern from cd-staging.yml instead of requiring
a separate GHCR_PAT secret. GITHUB_TOKEN is automatically available
in GitHub Actions and has the necessary permissions for ghcr.io.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
.github/workflows/cd-staging-tagged.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/cd-staging-tagged.yml b/.github/workflows/cd-staging-tagged.yml
index 525a6f023..a77f63f3e 100644
--- a/.github/workflows/cd-staging-tagged.yml
+++ b/.github/workflows/cd-staging-tagged.yml
@@ -309,7 +309,7 @@ jobs:
- name: Login to GHCR on staging server
run: |
ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
- echo "${{ secrets.GHCR_PAT }}" | docker login ghcr.io -u ${{ github.repository_owner }} --password-stdin
+ echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
EOF
- name: Deploy service
From aa8cbb166235ac01e0bd37c054c8fbc4010eb83c Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 18:41:45 +0100
Subject: [PATCH 16/34] fix(ci): correct health check path for backend
deployments
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The health check was using /health but all NestJS backends set
app.setGlobalPrefix('api/v1'), so the actual endpoint is /api/v1/health.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
.github/workflows/cd-staging-tagged.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/cd-staging-tagged.yml b/.github/workflows/cd-staging-tagged.yml
index a77f63f3e..45a1c201f 100644
--- a/.github/workflows/cd-staging-tagged.yml
+++ b/.github/workflows/cd-staging-tagged.yml
@@ -186,7 +186,7 @@ jobs:
*) PORT="5173" ;; # default SvelteKit port
esac
fi
- HEALTH_PATH="/health"
+ HEALTH_PATH="/api/v1/health"
;;
esac
From 17c4932fc55c45dc6b5d4815dbe95cda9e766e90 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 18:58:31 +0100
Subject: [PATCH 17/34] fix(todo-web): remove silent npm install failure in
Dockerfile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The production stage was silently ignoring npm install failures with
`|| true`, causing date-fns and other dependencies to be missing at runtime.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/todo/apps/web/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/todo/apps/web/Dockerfile b/apps/todo/apps/web/Dockerfile
index 740d41725..c041715e2 100644
--- a/apps/todo/apps/web/Dockerfile
+++ b/apps/todo/apps/web/Dockerfile
@@ -62,7 +62,7 @@ COPY --from=builder /app/apps/todo/apps/web/build ./build
COPY --from=builder /app/apps/todo/apps/web/package.json ./
# Install only production dependencies for the built app
-RUN npm install --omit=dev 2>/dev/null || true
+RUN npm install --omit=dev
# Expose port
EXPOSE 5188
From ef44c065f92b4ad9f9bace6a7b18525cd07ac5fe Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 18:59:08 +0100
Subject: [PATCH 18/34] fix(web): remove silent npm install failures in all web
Dockerfiles
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The production stage was silently ignoring npm install failures with
`|| true`, causing dependencies to be missing at runtime.
Affected: clock, manacore, chat, calendar web apps.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/calendar/apps/web/Dockerfile | 2 +-
apps/chat/apps/web/Dockerfile | 2 +-
apps/clock/apps/web/Dockerfile | 2 +-
apps/manacore/apps/web/Dockerfile | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/apps/calendar/apps/web/Dockerfile b/apps/calendar/apps/web/Dockerfile
index 94132a760..29581cae3 100644
--- a/apps/calendar/apps/web/Dockerfile
+++ b/apps/calendar/apps/web/Dockerfile
@@ -62,7 +62,7 @@ COPY --from=builder /app/apps/calendar/apps/web/build ./build
COPY --from=builder /app/apps/calendar/apps/web/package.json ./
# Install only production dependencies for the built app
-RUN npm install --omit=dev 2>/dev/null || true
+RUN npm install --omit=dev
# Expose port
EXPOSE 5186
diff --git a/apps/chat/apps/web/Dockerfile b/apps/chat/apps/web/Dockerfile
index cd19de658..b722cb801 100644
--- a/apps/chat/apps/web/Dockerfile
+++ b/apps/chat/apps/web/Dockerfile
@@ -62,7 +62,7 @@ COPY --from=builder /app/apps/chat/apps/web/build ./build
COPY --from=builder /app/apps/chat/apps/web/package.json ./
# Install only production dependencies for the built app
-RUN npm install --omit=dev 2>/dev/null || true
+RUN npm install --omit=dev
# Expose port
EXPOSE 3000
diff --git a/apps/clock/apps/web/Dockerfile b/apps/clock/apps/web/Dockerfile
index 10e51fa63..c3782396a 100644
--- a/apps/clock/apps/web/Dockerfile
+++ b/apps/clock/apps/web/Dockerfile
@@ -62,7 +62,7 @@ COPY --from=builder /app/apps/clock/apps/web/build ./build
COPY --from=builder /app/apps/clock/apps/web/package.json ./
# Install only production dependencies for the built app
-RUN npm install --omit=dev 2>/dev/null || true
+RUN npm install --omit=dev
# Expose port
EXPOSE 5187
diff --git a/apps/manacore/apps/web/Dockerfile b/apps/manacore/apps/web/Dockerfile
index 187511bc5..44b494c1b 100644
--- a/apps/manacore/apps/web/Dockerfile
+++ b/apps/manacore/apps/web/Dockerfile
@@ -63,7 +63,7 @@ COPY --from=builder /app/apps/manacore/apps/web/build ./build
COPY --from=builder /app/apps/manacore/apps/web/package.json ./
# Install only production dependencies for the built app
-RUN npm install --omit=dev 2>/dev/null || true
+RUN npm install --omit=dev
# Expose port
EXPOSE 5173
From 75d9d18e0dca71e3e1d02658d4484d8f0e507eff Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 19:24:52 +0100
Subject: [PATCH 19/34] fix(web): copy node_modules from builder instead of npm
install
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
npm doesn't understand pnpm's workspace:* protocol. Copy node_modules
from the builder stage to get all dependencies including workspace packages.
Affected: todo, manacore, chat, calendar, clock web apps.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/calendar/apps/web/Dockerfile | 6 ++----
apps/chat/apps/web/Dockerfile | 6 ++----
apps/clock/apps/web/Dockerfile | 6 ++----
apps/manacore/apps/web/Dockerfile | 6 ++----
apps/todo/apps/web/Dockerfile | 6 ++----
5 files changed, 10 insertions(+), 20 deletions(-)
diff --git a/apps/calendar/apps/web/Dockerfile b/apps/calendar/apps/web/Dockerfile
index 29581cae3..ed87318e7 100644
--- a/apps/calendar/apps/web/Dockerfile
+++ b/apps/calendar/apps/web/Dockerfile
@@ -57,12 +57,10 @@ FROM node:20-alpine AS production
WORKDIR /app
-# Copy built application
+# Copy built application and node_modules from builder
COPY --from=builder /app/apps/calendar/apps/web/build ./build
COPY --from=builder /app/apps/calendar/apps/web/package.json ./
-
-# Install only production dependencies for the built app
-RUN npm install --omit=dev
+COPY --from=builder /app/apps/calendar/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 5186
diff --git a/apps/chat/apps/web/Dockerfile b/apps/chat/apps/web/Dockerfile
index b722cb801..f4bf298fc 100644
--- a/apps/chat/apps/web/Dockerfile
+++ b/apps/chat/apps/web/Dockerfile
@@ -57,12 +57,10 @@ FROM node:20-alpine AS production
WORKDIR /app
-# Copy built application
+# Copy built application and node_modules from builder
COPY --from=builder /app/apps/chat/apps/web/build ./build
COPY --from=builder /app/apps/chat/apps/web/package.json ./
-
-# Install only production dependencies for the built app
-RUN npm install --omit=dev
+COPY --from=builder /app/apps/chat/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 3000
diff --git a/apps/clock/apps/web/Dockerfile b/apps/clock/apps/web/Dockerfile
index c3782396a..06b4bc901 100644
--- a/apps/clock/apps/web/Dockerfile
+++ b/apps/clock/apps/web/Dockerfile
@@ -57,12 +57,10 @@ FROM node:20-alpine AS production
WORKDIR /app
-# Copy built application
+# Copy built application and node_modules from builder
COPY --from=builder /app/apps/clock/apps/web/build ./build
COPY --from=builder /app/apps/clock/apps/web/package.json ./
-
-# Install only production dependencies for the built app
-RUN npm install --omit=dev
+COPY --from=builder /app/apps/clock/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 5187
diff --git a/apps/manacore/apps/web/Dockerfile b/apps/manacore/apps/web/Dockerfile
index 44b494c1b..f34e22bc1 100644
--- a/apps/manacore/apps/web/Dockerfile
+++ b/apps/manacore/apps/web/Dockerfile
@@ -58,12 +58,10 @@ FROM node:20-alpine AS production
WORKDIR /app
-# Copy built application
+# Copy built application and node_modules from builder
COPY --from=builder /app/apps/manacore/apps/web/build ./build
COPY --from=builder /app/apps/manacore/apps/web/package.json ./
-
-# Install only production dependencies for the built app
-RUN npm install --omit=dev
+COPY --from=builder /app/apps/manacore/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 5173
diff --git a/apps/todo/apps/web/Dockerfile b/apps/todo/apps/web/Dockerfile
index c041715e2..93ec5e7ee 100644
--- a/apps/todo/apps/web/Dockerfile
+++ b/apps/todo/apps/web/Dockerfile
@@ -57,12 +57,10 @@ FROM node:20-alpine AS production
WORKDIR /app
-# Copy built application
+# Copy built application and node_modules from builder
COPY --from=builder /app/apps/todo/apps/web/build ./build
COPY --from=builder /app/apps/todo/apps/web/package.json ./
-
-# Install only production dependencies for the built app
-RUN npm install --omit=dev
+COPY --from=builder /app/apps/todo/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 5188
From fd1c0ee6a297581a58287a9801c7672816966468 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 19:38:49 +0100
Subject: [PATCH 20/34] fix(docker): preserve pnpm symlink structure in web
Dockerfiles
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The production stage was copying node_modules to /app, but pnpm creates
symlinks pointing to ../../../../../node_modules/.pnpm/ (relative to the
app's node_modules location). When the directory structure changed, these
symlinks broke, causing "Cannot find package" errors.
Fix:
- Keep same directory structure in production (/app/apps/*/apps/web)
- Copy the root .pnpm store that symlinks point to
- Copy the app's node_modules which contains the symlinks
Affected apps: todo, manacore, chat, calendar, clock
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/calendar/apps/web/Dockerfile | 12 +++++++++---
apps/chat/apps/web/Dockerfile | 12 +++++++++---
apps/clock/apps/web/Dockerfile | 12 +++++++++---
apps/manacore/apps/web/Dockerfile | 12 +++++++++---
apps/todo/apps/web/Dockerfile | 12 +++++++++---
5 files changed, 45 insertions(+), 15 deletions(-)
diff --git a/apps/calendar/apps/web/Dockerfile b/apps/calendar/apps/web/Dockerfile
index ed87318e7..53c964525 100644
--- a/apps/calendar/apps/web/Dockerfile
+++ b/apps/calendar/apps/web/Dockerfile
@@ -55,12 +55,18 @@ RUN pnpm build
# Production stage
FROM node:20-alpine AS production
-WORKDIR /app
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/calendar/apps/web
-# Copy built application and node_modules from builder
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/calendar/apps/web/node_modules ./node_modules
+
+# Copy built application
COPY --from=builder /app/apps/calendar/apps/web/build ./build
COPY --from=builder /app/apps/calendar/apps/web/package.json ./
-COPY --from=builder /app/apps/calendar/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 5186
diff --git a/apps/chat/apps/web/Dockerfile b/apps/chat/apps/web/Dockerfile
index f4bf298fc..544d462a2 100644
--- a/apps/chat/apps/web/Dockerfile
+++ b/apps/chat/apps/web/Dockerfile
@@ -55,12 +55,18 @@ RUN pnpm build
# Production stage
FROM node:20-alpine AS production
-WORKDIR /app
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/chat/apps/web
-# Copy built application and node_modules from builder
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/chat/apps/web/node_modules ./node_modules
+
+# Copy built application
COPY --from=builder /app/apps/chat/apps/web/build ./build
COPY --from=builder /app/apps/chat/apps/web/package.json ./
-COPY --from=builder /app/apps/chat/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 3000
diff --git a/apps/clock/apps/web/Dockerfile b/apps/clock/apps/web/Dockerfile
index 06b4bc901..2f5e6c366 100644
--- a/apps/clock/apps/web/Dockerfile
+++ b/apps/clock/apps/web/Dockerfile
@@ -55,12 +55,18 @@ RUN pnpm build
# Production stage
FROM node:20-alpine AS production
-WORKDIR /app
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/clock/apps/web
-# Copy built application and node_modules from builder
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/clock/apps/web/node_modules ./node_modules
+
+# Copy built application
COPY --from=builder /app/apps/clock/apps/web/build ./build
COPY --from=builder /app/apps/clock/apps/web/package.json ./
-COPY --from=builder /app/apps/clock/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 5187
diff --git a/apps/manacore/apps/web/Dockerfile b/apps/manacore/apps/web/Dockerfile
index f34e22bc1..b400e79d5 100644
--- a/apps/manacore/apps/web/Dockerfile
+++ b/apps/manacore/apps/web/Dockerfile
@@ -56,12 +56,18 @@ RUN pnpm build
# Production stage
FROM node:20-alpine AS production
-WORKDIR /app
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/manacore/apps/web
-# Copy built application and node_modules from builder
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/manacore/apps/web/node_modules ./node_modules
+
+# Copy built application
COPY --from=builder /app/apps/manacore/apps/web/build ./build
COPY --from=builder /app/apps/manacore/apps/web/package.json ./
-COPY --from=builder /app/apps/manacore/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 5173
diff --git a/apps/todo/apps/web/Dockerfile b/apps/todo/apps/web/Dockerfile
index 93ec5e7ee..e29ff1815 100644
--- a/apps/todo/apps/web/Dockerfile
+++ b/apps/todo/apps/web/Dockerfile
@@ -55,12 +55,18 @@ RUN pnpm build
# Production stage
FROM node:20-alpine AS production
-WORKDIR /app
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/todo/apps/web
-# Copy built application and node_modules from builder
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/todo/apps/web/node_modules ./node_modules
+
+# Copy built application
COPY --from=builder /app/apps/todo/apps/web/build ./build
COPY --from=builder /app/apps/todo/apps/web/package.json ./
-COPY --from=builder /app/apps/todo/apps/web/node_modules ./node_modules
# Expose port
EXPOSE 5188
From 828b6d71a184e79aefbd856c738e71d87dfc0b32 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 19:51:14 +0100
Subject: [PATCH 21/34] docs: add pnpm Docker symlinks troubleshooting guide
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Documented the "Cannot find package" error that occurs when pnpm symlinks
break in Docker containers due to directory structure changes.
Includes:
- Root cause explanation (symlinks point to .pnpm store)
- Wrong approach (flattening directory structure)
- Correct approach (preserve structure + copy .pnpm store)
- Debug commands
- Trade-offs (image size vs functionality)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
TROUBLESHOOTING.md | 129 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 129 insertions(+)
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index aafb6b21d..db84b689c 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -16,6 +16,7 @@ Common issues and solutions for the manacore-monorepo.
- [Client-Side Calling localhost Instead of Public IP](#problem-5-client-side-calling-localhost-instead-of-public-ip)
- [CORS Blocking Cross-Origin Requests](#problem-6-cors-blocking-cross-origin-requests)
- [Missing Database Schema](#problem-7-missing-database-schema)
+ - [pnpm Symlinks Broken in Docker Container](#problem-8-pnpm-symlinks-broken-in-docker-container)
---
@@ -922,6 +923,134 @@ docker compose exec -T chat-backend wget -q -O - http://localhost:3002/api/v1/he
12. **Drizzle Kit Interactive Mode:** `drizzle-kit push` prompts for confirmation. Use `--force` flag in CI/CD to skip interactive mode.
+13. **pnpm Symlinks in Docker:** pnpm uses symlinks to a central `.pnpm` store. When copying `node_modules` in Docker, you must preserve both the symlinks AND the target directory they point to. See [Problem 8](#problem-8-pnpm-symlinks-broken-in-docker-container).
+
+---
+
+### Problem 8: pnpm Symlinks Broken in Docker Container
+
+**Symptoms:**
+
+- Container starts but crashes with: `Cannot find package 'date-fns' imported from /app/build/server/chunks/_page.svelte-xxx.js`
+- Error `ERR_MODULE_NOT_FOUND` for packages that ARE in node_modules
+- Works locally but fails in Docker production stage
+- `ls node_modules/date-fns` shows a symlink pointing to `../../../../../node_modules/.pnpm/...`
+
+**Root Cause:**
+
+pnpm uses symlinks to a central `.pnpm` store at the monorepo root. When you copy only the app's `node_modules` in Docker, the symlinks point to paths that don't exist:
+
+```
+# In builder stage (pnpm workspace):
+/app/apps/todo/apps/web/node_modules/date-fns → ../../../../../node_modules/.pnpm/date-fns@4.1.0/node_modules/date-fns
+
+# In production stage (old broken approach):
+/app/node_modules/date-fns → ../../../../../node_modules/.pnpm/...
+# ↑ BROKEN! This path doesn't exist because we only copied to /app/
+```
+
+**❌ WRONG - Flattening Directory Structure:**
+
+```dockerfile
+# Production stage
+FROM node:20-alpine AS production
+
+WORKDIR /app # ❌ Different from builder structure
+
+# Copy node_modules (symlinks will be broken!)
+COPY --from=builder /app/apps/todo/apps/web/node_modules ./node_modules # ❌ BROKEN
+COPY --from=builder /app/apps/todo/apps/web/build ./build
+COPY --from=builder /app/apps/todo/apps/web/package.json ./
+
+CMD ["node", "build"]
+```
+
+The symlinks in `node_modules` point to `../../../../../node_modules/.pnpm/...` which resolves to a non-existent path from `/app/`.
+
+**✅ CORRECT - Preserve Directory Structure + Copy .pnpm Store:**
+
+```dockerfile
+# Production stage
+FROM node:20-alpine AS production
+
+# Keep same directory structure as builder so pnpm symlinks resolve correctly
+WORKDIR /app/apps/todo/apps/web # ✅ Same as builder
+
+# Copy the pnpm store that symlinks point to (at /app/node_modules/.pnpm)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm # ✅ Target of symlinks
+
+# Copy the app's node_modules (contains symlinks to the pnpm store)
+COPY --from=builder /app/apps/todo/apps/web/node_modules ./node_modules # ✅ Symlinks work now
+
+# Copy built application
+COPY --from=builder /app/apps/todo/apps/web/build ./build
+COPY --from=builder /app/apps/todo/apps/web/package.json ./
+
+CMD ["node", "build"]
+```
+
+**Why This Works:**
+
+1. `WORKDIR /app/apps/todo/apps/web` - Production container has same path as builder
+2. Symlinks in `./node_modules/` point to `../../../../../node_modules/.pnpm/...`
+3. From `/app/apps/todo/apps/web/node_modules/`, going up 5 directories reaches `/app/`
+4. `/app/node_modules/.pnpm/` exists because we copied it!
+
+**How to Debug:**
+
+```bash
+# Check if symlinks are broken in the container
+docker exec ls -la node_modules/date-fns
+# Shows: date-fns -> ../../../../../node_modules/.pnpm/date-fns@4.1.0/node_modules/date-fns
+
+# Check if the target exists
+docker exec ls -la /app/node_modules/.pnpm/date-fns@4.1.0/
+# If "No such file or directory" → symlink is broken
+
+# Check the image size (should be ~1GB with .pnpm store, ~50MB without)
+docker images | grep todo-web
+```
+
+**Trade-offs:**
+
+| Approach | Image Size | Symlinks | Works |
+| ------------------------------------- | ---------- | -------- | ----- |
+| Copy only app's node_modules | ~50MB | Broken | ❌ |
+| Copy app's node_modules + .pnpm store | ~1GB | Working | ✅ |
+
+The larger image size is the cost of pnpm's deduplication strategy. In a monorepo, this is actually more efficient than copying all dependencies flat.
+
+**Alternative: Use npm Instead of pnpm in Docker:**
+
+If image size is critical, you could use npm in the Docker build:
+
+```dockerfile
+# Alternative approach (not recommended for monorepos)
+FROM node:20-alpine AS production
+WORKDIR /app
+COPY --from=builder /app/apps/todo/apps/web/build ./build
+COPY --from=builder /app/apps/todo/apps/web/package.json ./
+
+# Clean install with npm (flattens dependencies)
+RUN npm install --omit=dev
+
+CMD ["node", "build"]
+```
+
+⚠️ **Warning:** This may fail with `workspace:*` protocol in package.json dependencies. Only works if all dependencies are published to npm.
+
+**Affected Files:**
+
+- `apps/todo/apps/web/Dockerfile`
+- `apps/manacore/apps/web/Dockerfile`
+- `apps/chat/apps/web/Dockerfile`
+- `apps/calendar/apps/web/Dockerfile`
+- `apps/clock/apps/web/Dockerfile`
+
+**Related Commits:**
+
+- `fd1c0ee6` - fix(docker): preserve pnpm symlink structure in web Dockerfiles
+
---
## References
From 3d717eb16a1a8cbb7ec31d685deb954a96f918b4 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 20:49:39 +0100
Subject: [PATCH 22/34] fix(web): add runtime env injection for auth URLs in
todo, calendar, clock
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
All three apps were hardcoding localhost:3001 for auth, which broke
staging deployments. Now uses hooks.server.ts to inject the correct
PUBLIC_MANA_CORE_AUTH_URL_CLIENT at runtime (same pattern as chat-web).
Changes:
- Add hooks.server.ts to todo, calendar, clock web apps
- Update auth.svelte.ts to use window.__PUBLIC_MANA_CORE_AUTH_URL__
- Update user-settings.svelte.ts to use runtime URL
- Fix feedback services to use runtime URL
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/calendar/apps/web/src/hooks.server.ts | 27 +++++++++++++++++++
.../apps/web/src/lib/services/feedback.ts | 14 +++++++---
.../apps/web/src/lib/stores/auth.svelte.ts | 16 ++++++++---
.../src/lib/stores/user-settings.svelte.ts | 13 +++++++--
apps/clock/apps/web/src/hooks.server.ts | 27 +++++++++++++++++++
.../apps/web/src/lib/stores/auth.svelte.ts | 16 ++++++++---
.../src/lib/stores/user-settings.svelte.ts | 13 +++++++--
.../src/routes/(app)/feedback/+page.svelte | 13 ++++++++-
apps/todo/apps/web/src/hooks.server.ts | 27 +++++++++++++++++++
.../apps/web/src/lib/stores/auth.svelte.ts | 16 ++++++++---
.../src/lib/stores/user-settings.svelte.ts | 13 +++++++--
11 files changed, 176 insertions(+), 19 deletions(-)
create mode 100644 apps/calendar/apps/web/src/hooks.server.ts
create mode 100644 apps/clock/apps/web/src/hooks.server.ts
create mode 100644 apps/todo/apps/web/src/hooks.server.ts
diff --git a/apps/calendar/apps/web/src/hooks.server.ts b/apps/calendar/apps/web/src/hooks.server.ts
new file mode 100644
index 000000000..6d7a5089d
--- /dev/null
+++ b/apps/calendar/apps/web/src/hooks.server.ts
@@ -0,0 +1,27 @@
+/**
+ * Server Hooks for SvelteKit
+ * - Injects runtime environment variables for client-side use
+ * - Auth is handled client-side via Mana Core Auth
+ */
+
+import type { Handle } from '@sveltejs/kit';
+
+// Get client-side URLs from environment (Docker runtime)
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ // Inject runtime environment variables into the HTML
+ // These will be available on window.__PUBLIC_*__ for client-side code
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
diff --git a/apps/calendar/apps/web/src/lib/services/feedback.ts b/apps/calendar/apps/web/src/lib/services/feedback.ts
index 5f86dbd7c..de6a8a6d0 100644
--- a/apps/calendar/apps/web/src/lib/services/feedback.ts
+++ b/apps/calendar/apps/web/src/lib/services/feedback.ts
@@ -2,14 +2,22 @@
* Feedback Service Instance for Calendar Web App
*/
+import { browser } from '$app/environment';
import { createFeedbackService } from '@manacore/shared-feedback-service';
import { authStore } from '$lib/stores/auth.svelte';
-import { PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
-const MANA_AUTH_URL = PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const feedbackService = createFeedbackService({
- apiUrl: MANA_AUTH_URL,
+ apiUrl: getAuthUrl(),
appId: 'calendar',
getAuthToken: async () => authStore.getAccessToken(),
});
diff --git a/apps/calendar/apps/web/src/lib/stores/auth.svelte.ts b/apps/calendar/apps/web/src/lib/stores/auth.svelte.ts
index da5eceae2..874675890 100644
--- a/apps/calendar/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/calendar/apps/web/src/lib/stores/auth.svelte.ts
@@ -7,8 +7,18 @@ import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
-// Initialize Mana Core Auth only on the client side
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime - fallback for SSR and client
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ // Falls back to localhost for local development
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL for container-to-container communication
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType['authService'] | null = null;
@@ -17,7 +27,7 @@ let _tokenManager: ReturnType['tokenManager'] | null =
function getAuthService() {
if (!browser) return null;
if (!_authService) {
- const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
+ const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
diff --git a/apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts b/apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts
index 6cc7b62ed..a2e5e08b7 100644
--- a/apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts
+++ b/apps/calendar/apps/web/src/lib/stores/user-settings.svelte.ts
@@ -7,13 +7,22 @@
* - localStorage caching for offline support
*/
+import { browser } from '$app/environment';
import { createUserSettingsStore } from '@manacore/shared-theme';
import { authStore } from './auth.svelte';
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const userSettings = createUserSettingsStore({
appId: 'calendar',
- authUrl: MANA_AUTH_URL,
+ authUrl: getAuthUrl(),
getAccessToken: () => authStore.getAccessToken(),
});
diff --git a/apps/clock/apps/web/src/hooks.server.ts b/apps/clock/apps/web/src/hooks.server.ts
new file mode 100644
index 000000000..6d7a5089d
--- /dev/null
+++ b/apps/clock/apps/web/src/hooks.server.ts
@@ -0,0 +1,27 @@
+/**
+ * Server Hooks for SvelteKit
+ * - Injects runtime environment variables for client-side use
+ * - Auth is handled client-side via Mana Core Auth
+ */
+
+import type { Handle } from '@sveltejs/kit';
+
+// Get client-side URLs from environment (Docker runtime)
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ // Inject runtime environment variables into the HTML
+ // These will be available on window.__PUBLIC_*__ for client-side code
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
diff --git a/apps/clock/apps/web/src/lib/stores/auth.svelte.ts b/apps/clock/apps/web/src/lib/stores/auth.svelte.ts
index 555153459..6be177cbe 100644
--- a/apps/clock/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/clock/apps/web/src/lib/stores/auth.svelte.ts
@@ -6,8 +6,18 @@
import { browser } from '$app/environment';
import { initializeWebAuth, type UserData } from '@manacore/shared-auth';
-// Initialize Mana Core Auth only on the client side
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime - fallback for SSR and client
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ // Falls back to localhost for local development
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL for container-to-container communication
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType['authService'] | null = null;
@@ -16,7 +26,7 @@ let _tokenManager: ReturnType['tokenManager'] | null =
function getAuthService() {
if (!browser) return null;
if (!_authService) {
- const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
+ const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
diff --git a/apps/clock/apps/web/src/lib/stores/user-settings.svelte.ts b/apps/clock/apps/web/src/lib/stores/user-settings.svelte.ts
index 3b9727cdc..f88fb5453 100644
--- a/apps/clock/apps/web/src/lib/stores/user-settings.svelte.ts
+++ b/apps/clock/apps/web/src/lib/stores/user-settings.svelte.ts
@@ -7,13 +7,22 @@
* - localStorage caching for offline support
*/
+import { browser } from '$app/environment';
import { createUserSettingsStore } from '@manacore/shared-theme';
import { authStore } from './auth.svelte';
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const userSettings = createUserSettingsStore({
appId: 'clock',
- authUrl: MANA_AUTH_URL,
+ authUrl: getAuthUrl(),
getAccessToken: () => authStore.getAccessToken(),
});
diff --git a/apps/clock/apps/web/src/routes/(app)/feedback/+page.svelte b/apps/clock/apps/web/src/routes/(app)/feedback/+page.svelte
index 1a2521e87..1d3c8d9c2 100644
--- a/apps/clock/apps/web/src/routes/(app)/feedback/+page.svelte
+++ b/apps/clock/apps/web/src/routes/(app)/feedback/+page.svelte
@@ -1,12 +1,23 @@
`;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
diff --git a/apps/todo/apps/web/src/lib/stores/auth.svelte.ts b/apps/todo/apps/web/src/lib/stores/auth.svelte.ts
index 1d2fd96d9..885a63266 100644
--- a/apps/todo/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/todo/apps/web/src/lib/stores/auth.svelte.ts
@@ -7,8 +7,18 @@ import { browser } from '$app/environment';
import { initializeWebAuth, type UserData } from '@manacore/shared-auth';
import { apiClient } from '$lib/api/client';
-// Initialize Mana Core Auth only on the client side
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime - fallback for SSR and client
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ // Falls back to localhost for local development
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL for container-to-container communication
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType['authService'] | null = null;
@@ -17,7 +27,7 @@ let _tokenManager: ReturnType['tokenManager'] | null =
function getAuthService() {
if (!browser) return null;
if (!_authService) {
- const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
+ const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
diff --git a/apps/todo/apps/web/src/lib/stores/user-settings.svelte.ts b/apps/todo/apps/web/src/lib/stores/user-settings.svelte.ts
index 18af057b7..536074626 100644
--- a/apps/todo/apps/web/src/lib/stores/user-settings.svelte.ts
+++ b/apps/todo/apps/web/src/lib/stores/user-settings.svelte.ts
@@ -1,10 +1,19 @@
+import { browser } from '$app/environment';
import { createUserSettingsStore } from '@manacore/shared-theme';
import { authStore } from './auth.svelte';
-const MANA_AUTH_URL = import.meta.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const userSettings = createUserSettingsStore({
appId: 'todo',
- authUrl: MANA_AUTH_URL,
+ authUrl: getAuthUrl(),
getAccessToken: () => authStore.getAccessToken(),
});
From 7f7b8b6db0cf74a3b0b2ae2dae2dbe9d6ec2c546 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 20:54:13 +0100
Subject: [PATCH 23/34] docs: add SvelteKit runtime env injection guidelines
and troubleshooting
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add comprehensive guide in .claude/guidelines/sveltekit-web.md about
build-time vs runtime environment variables for Docker deployments
- Document the correct hooks.server.ts + window injection pattern
- Add Problem 9 to TROUBLESHOOTING.md for hardcoded localhost URLs
- List which apps are fixed vs need fixing
- Add checklist for fixing new apps
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
.claude/guidelines/sveltekit-web.md | 131 +++++++++++++++++++++++++++-
TROUBLESHOOTING.md | 96 ++++++++++++++++++++
2 files changed, 223 insertions(+), 4 deletions(-)
diff --git a/.claude/guidelines/sveltekit-web.md b/.claude/guidelines/sveltekit-web.md
index a307c9352..4a69dd273 100644
--- a/.claude/guidelines/sveltekit-web.md
+++ b/.claude/guidelines/sveltekit-web.md
@@ -698,15 +698,138 @@ export default {
## Environment Variables
-```typescript
-// Access in .svelte or .ts files
-import { PUBLIC_BACKEND_URL, PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
+### Build-Time vs Runtime Variables
-// .env file
+SvelteKit has **two types** of environment variables:
+
+1. **Build-time** (`$env/static/public`) - Baked into the bundle at build time
+2. **Runtime** (`process.env`) - Available at runtime in server code
+
+**CRITICAL**: For Docker deployments, browser-facing URLs must use **runtime injection** because:
+
+- Docker images are built once but deployed to different environments (staging, production)
+- Build-time variables would require rebuilding the image for each environment
+- The browser cannot access `process.env` - it needs values injected into the HTML
+
+### ❌ WRONG - Hardcoded or Build-Time URLs
+
+```typescript
+// ❌ BAD - Hardcoded URL (won't work in Docker)
+const MANA_AUTH_URL = 'http://localhost:3001';
+
+// ❌ BAD - Build-time variable (works locally, breaks in Docker)
+import { PUBLIC_MANA_CORE_AUTH_URL } from '$env/static/public';
+const MANA_AUTH_URL = PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+
+// ❌ BAD - import.meta.env is also build-time
+const MANA_AUTH_URL = import.meta.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+```
+
+### ✅ CORRECT - Runtime Injection Pattern
+
+**Step 1: Create `hooks.server.ts`** to inject env vars into HTML:
+
+```typescript
+// src/hooks.server.ts
+import type { Handle } from '@sveltejs/kit';
+
+// Get client-side URLs from Docker runtime environment
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ // Inject runtime environment variables into the HTML
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
+```
+
+**Step 2: Read from `window` in client code:**
+
+```typescript
+// src/lib/stores/auth.svelte.ts
+import { browser } from '$app/environment';
+
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
+
+// Use in auth service initialization
+const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
+```
+
+**Step 3: Set environment variables in `docker-compose.staging.yml`:**
+
+```yaml
+services:
+ myapp-web:
+ environment:
+ # Server-side URLs (Docker internal network)
+ PUBLIC_BACKEND_URL: http://myapp-backend:3000
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ # Client-side URLs (browser access via public IP)
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3000
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+```
+
+### Why Two URLs?
+
+| Variable | Purpose | Example |
+| ---------------------------------- | --------------------------------- | ---------------------------- |
+| `PUBLIC_MANA_CORE_AUTH_URL` | Server-to-server (SSR, API calls) | `http://mana-core-auth:3001` |
+| `PUBLIC_MANA_CORE_AUTH_URL_CLIENT` | Browser to server | `http://46.224.108.214:3001` |
+
+Docker containers can reach each other by service name (`mana-core-auth`), but browsers need the public IP/domain.
+
+### Apps Using This Pattern Correctly
+
+- ✅ `chat/apps/web` - Has `hooks.server.ts` with runtime injection
+- ✅ `todo/apps/web` - Fixed
+- ✅ `calendar/apps/web` - Fixed
+- ✅ `clock/apps/web` - Fixed
+
+### Apps That Still Need Fixing
+
+- ❌ `contacts/apps/web`
+- ❌ `manadeck/apps/web`
+- ❌ `manacore/apps/web`
+- ❌ `zitare/apps/web`
+- ❌ `picture/apps/web`
+
+### Quick Checklist for New SvelteKit Apps
+
+- [ ] Create `src/hooks.server.ts` with env injection
+- [ ] Update `auth.svelte.ts` to use `getAuthUrl()` pattern
+- [ ] Update `user-settings.svelte.ts` to use `getAuthUrl()` pattern
+- [ ] Update any feedback services to use runtime URL
+- [ ] Add both `_CLIENT` and non-client env vars to `docker-compose.staging.yml`
+- [ ] Never hardcode `localhost:3001` anywhere
+
+### Simple .env (for local development only)
+
+```env
PUBLIC_BACKEND_URL=http://localhost:3016
PUBLIC_MANA_CORE_AUTH_URL=http://localhost:3001
```
+These work locally because both the browser and server access `localhost`.
+
## Anti-Patterns to Avoid
### Don't Use Old Svelte Syntax
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
index db84b689c..a84dcb359 100644
--- a/TROUBLESHOOTING.md
+++ b/TROUBLESHOOTING.md
@@ -17,6 +17,7 @@ Common issues and solutions for the manacore-monorepo.
- [CORS Blocking Cross-Origin Requests](#problem-6-cors-blocking-cross-origin-requests)
- [Missing Database Schema](#problem-7-missing-database-schema)
- [pnpm Symlinks Broken in Docker Container](#problem-8-pnpm-symlinks-broken-in-docker-container)
+ - [Hardcoded localhost URLs in SvelteKit Web Apps](#problem-9-hardcoded-localhost-urls-in-sveltekit-web-apps)
---
@@ -1053,6 +1054,101 @@ CMD ["node", "build"]
---
+### Problem 9: Hardcoded localhost URLs in SvelteKit Web Apps
+
+**Symptoms:**
+
+- Browser console shows: `POST http://localhost:3001/api/v1/auth/login net::ERR_CONNECTION_REFUSED`
+- App works locally but auth fails on staging/production
+- The `window.__PUBLIC_MANA_CORE_AUTH_URL__` may be set correctly, but code doesn't use it
+- Looking at the source code reveals hardcoded URLs like `const MANA_AUTH_URL = 'http://localhost:3001'`
+
+**Root Cause:**
+
+Developers hardcode `localhost:3001` directly in TypeScript files instead of using the runtime injection pattern. This works locally but breaks in Docker deployments.
+
+**Common Locations of Hardcoded URLs:**
+
+```typescript
+// ❌ These patterns are WRONG:
+
+// In auth.svelte.ts
+const MANA_AUTH_URL = 'http://localhost:3001';
+
+// In user-settings.svelte.ts
+const MANA_AUTH_URL = 'http://localhost:3001';
+
+// In feedback.ts or feedback page
+apiUrl: 'http://localhost:3001',
+
+// Using build-time env vars (also wrong for Docker)
+const MANA_AUTH_URL = import.meta.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+```
+
+**Solution:**
+
+1. **Create `hooks.server.ts`** if it doesn't exist (see Problem 5)
+2. **Use `getAuthUrl()` pattern in all files:**
+
+```typescript
+// ✅ CORRECT pattern
+import { browser } from '$app/environment';
+
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
+
+// Use getAuthUrl() instead of hardcoded string
+const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
+```
+
+**How to Find Hardcoded URLs:**
+
+```bash
+# Search for hardcoded localhost:3001 in web apps
+grep -r "localhost:3001" apps/*/apps/web/src --include="*.ts" --include="*.svelte"
+
+# Check for the correct pattern (window injection)
+grep -r "__PUBLIC_MANA_CORE_AUTH_URL__" apps/*/apps/web/src
+```
+
+**Apps Status (as of 2024-12):**
+
+| App | Status | Files to Fix |
+| ------------------- | ------------ | ---------------------------------------------------------------- |
+| `chat/apps/web` | ✅ Fixed | - |
+| `todo/apps/web` | ✅ Fixed | - |
+| `calendar/apps/web` | ✅ Fixed | - |
+| `clock/apps/web` | ✅ Fixed | - |
+| `contacts/apps/web` | ❌ Needs Fix | auth.svelte.ts, user-settings.svelte.ts, feedback.ts |
+| `manadeck/apps/web` | ❌ Needs Fix | user-settings.svelte.ts, feedback.ts |
+| `manacore/apps/web` | ❌ Needs Fix | auth.svelte.ts, user-settings.svelte.ts, feedback.ts, credits.ts |
+| `zitare/apps/web` | ❌ Needs Fix | auth.svelte.ts, user-settings.svelte.ts, feedback.ts |
+| `picture/apps/web` | ❌ Needs Fix | user-settings.svelte.ts |
+
+**Complete Fix Checklist for Each App:**
+
+- [ ] Create/update `src/hooks.server.ts` with env injection
+- [ ] Update `src/lib/stores/auth.svelte.ts` → use `getAuthUrl()` pattern
+- [ ] Update `src/lib/stores/user-settings.svelte.ts` → use `getAuthUrl()` pattern
+- [ ] Update any `feedback.ts` or feedback services → use `getAuthUrl()` pattern
+- [ ] Update any other files with hardcoded URLs
+- [ ] Add `PUBLIC_MANA_CORE_AUTH_URL_CLIENT` to `docker-compose.staging.yml`
+- [ ] Test locally with `pnpm dev`
+- [ ] Deploy and test on staging
+
+**See Also:**
+
+- [Problem 5: Client-Side Calling localhost Instead of Public IP](#problem-5-client-side-calling-localhost-instead-of-public-ip)
+- [SvelteKit Web Guidelines - Environment Variables](/.claude/guidelines/sveltekit-web.md#environment-variables)
+
+---
+
## References
- [CLAUDE.md - Turborepo Configuration](./CLAUDE.md#turborepo-configuration)
From c96820daf736d1f817bdce5a1561f2b182492c23 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 21:23:36 +0100
Subject: [PATCH 24/34] fix(ci): pass version tags to docker-compose via .env
file
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The CD workflow was pulling the correct versioned image but docker-compose
was using the default 'latest' tag because version variables weren't being
set. Now the workflow:
1. Computes the correct version variable name (e.g., TODO_WEB_VERSION)
2. Updates the .env file on the staging server with the version
3. docker-compose reads from .env and uses the correct image tag
4. Verifies the correct image is running after deployment
This fixes deployments where the container would keep running an old
image even after a new version was pushed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
.github/workflows/cd-staging-tagged.yml | 41 +++++++++++++++++++++++--
1 file changed, 39 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/cd-staging-tagged.yml b/.github/workflows/cd-staging-tagged.yml
index 45a1c201f..5efce798b 100644
--- a/.github/workflows/cd-staging-tagged.yml
+++ b/.github/workflows/cd-staging-tagged.yml
@@ -316,21 +316,54 @@ jobs:
env:
VERSION: ${{ needs.parse-deployment.outputs.version }}
IMAGE_NAME: ${{ matrix.image_name }}
+ APP_TYPE: ${{ matrix.app }}
+ PROJECT: ${{ needs.parse-deployment.outputs.project }}
run: |
+ # Compute the version variable name locally (before SSH)
+ # Map: todo-web -> TODO_WEB_VERSION, chat-backend -> CHAT_VERSION
+ case "$IMAGE_NAME" in
+ *-web)
+ PROJECT_UPPER=$(echo "$PROJECT" | tr '[:lower:]-' '[:upper:]_')
+ VERSION_VAR="${PROJECT_UPPER}_WEB_VERSION"
+ ;;
+ *-backend)
+ PROJECT_UPPER=$(echo "$PROJECT" | tr '[:lower:]-' '[:upper:]_')
+ VERSION_VAR="${PROJECT_UPPER}_VERSION"
+ ;;
+ mana-core-auth)
+ VERSION_VAR="AUTH_VERSION"
+ ;;
+ *)
+ VERSION_VAR=$(echo "$IMAGE_NAME" | tr '[:lower:]-' '[:upper:]_')_VERSION
+ ;;
+ esac
+
+ echo "Will set $VERSION_VAR=$VERSION for docker-compose"
+
ssh ${{ secrets.STAGING_USER }}@${{ secrets.STAGING_HOST }} << EOF
cd ~/manacore-staging
echo "Deploying $IMAGE_NAME:$VERSION to staging..."
- # Pull the new image
+ # Pull the new image with specific version tag
docker pull ${{ env.IMAGE_PREFIX }}/$IMAGE_NAME:$VERSION
+ # Update .env file with the version for this service
+ # This ensures docker-compose uses the correct image tag
+ if grep -q "^$VERSION_VAR=" .env 2>/dev/null; then
+ sed -i "s/^$VERSION_VAR=.*/$VERSION_VAR=$VERSION/" .env
+ else
+ echo "$VERSION_VAR=$VERSION" >> .env
+ fi
+
+ echo "Updated .env: $VERSION_VAR=$VERSION"
+ grep "$VERSION_VAR" .env || true
+
# Service name matches docker-compose service name (with hyphens)
SERVICE_NAME="$IMAGE_NAME"
if docker compose ps -a | grep -q "$IMAGE_NAME"; then
echo "Updating existing service: \$SERVICE_NAME"
- docker compose pull \$SERVICE_NAME || true
docker compose up -d --no-deps --force-recreate \$SERVICE_NAME
else
echo "Service \$SERVICE_NAME not found in compose, starting..."
@@ -341,6 +374,10 @@ jobs:
sleep 10
docker compose ps \$SERVICE_NAME
+ # Verify correct image is running
+ echo "Running image:"
+ docker inspect --format='{{.Config.Image}}' ${IMAGE_NAME}-staging 2>/dev/null || true
+
# Cleanup old images
docker image prune -f
EOF
From e02e2b37b156bb529fd26999b7cf9a2cb5c59c37 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 21:28:06 +0100
Subject: [PATCH 25/34] fix(staging): add all web app origins to mana-core-auth
CORS
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Added CORS origins for all staging web apps:
- manacore-web: 5173
- calendar-web: 5186
- clock-web: 5187
- todo-web: 5188
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
docker-compose.staging.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml
index a8d5b5d97..7e0d1940a 100644
--- a/docker-compose.staging.yml
+++ b/docker-compose.staging.yml
@@ -74,8 +74,8 @@ services:
JWT_SECRET: ${JWT_SECRET}
JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY}
JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY}
- # CORS - Allow chat-web and other staging origins
- CORS_ORIGINS: http://46.224.108.214:3000,http://46.224.108.214:3002,http://localhost:3000
+ # CORS - Allow all staging web app origins
+ CORS_ORIGINS: http://46.224.108.214:3000,http://46.224.108.214:5173,http://46.224.108.214:5186,http://46.224.108.214:5187,http://46.224.108.214:5188,http://localhost:3000,http://localhost:5173,http://localhost:5186,http://localhost:5187,http://localhost:5188
ports:
- "3001:3001"
healthcheck:
From 7caeea4abdb2ea9d3c7301ea8d15708ea8ef888d Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 21:47:02 +0100
Subject: [PATCH 26/34] fix(manacore-web): add runtime env injection for auth
URLs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Same fix as todo, calendar, clock - uses hooks.server.ts to inject
PUBLIC_MANA_CORE_AUTH_URL_CLIENT at runtime for Docker deployments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/manacore/apps/web/src/hooks.server.ts | 18 +++++++++++++++---
.../apps/web/src/lib/stores/auth.svelte.ts | 17 +++++++++++++----
.../web/src/lib/stores/user-settings.svelte.ts | 14 +++++++++++---
3 files changed, 39 insertions(+), 10 deletions(-)
diff --git a/apps/manacore/apps/web/src/hooks.server.ts b/apps/manacore/apps/web/src/hooks.server.ts
index 446ce160c..ba1b0b200 100644
--- a/apps/manacore/apps/web/src/hooks.server.ts
+++ b/apps/manacore/apps/web/src/hooks.server.ts
@@ -3,9 +3,21 @@ import type { Handle } from '@sveltejs/kit';
/**
* Server hooks for ManaCore web app
*
- * Authentication is handled entirely by Mana Core Auth (@manacore/shared-auth).
- * No Supabase is needed - all data comes from mana-core-auth APIs.
+ * Injects runtime environment variables into the HTML for client-side access.
+ * This is necessary because SvelteKit's $env/static/public bakes values at build time,
+ * but Docker containers need runtime configuration.
*/
+
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+
export const handle: Handle = async ({ event, resolve }) => {
- return resolve(event);
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
};
diff --git a/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts b/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
index 5b6edc77a..126070f7b 100644
--- a/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
+++ b/apps/manacore/apps/web/src/lib/stores/auth.svelte.ts
@@ -7,9 +7,18 @@ import { browser } from '$app/environment';
import { initializeWebAuth } from '@manacore/shared-auth';
import type { UserData } from '@manacore/shared-auth';
-// Initialize Mana Core Auth only on the client side
-// TODO: Use PUBLIC_MANA_CORE_AUTH_URL from env when available
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime - fallback for SSR and client
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ // Falls back to localhost for local development
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ // Server-side (SSR): use Docker internal URL for container-to-container communication
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
// Lazy initialization to avoid SSR issues with localStorage
let _authService: ReturnType['authService'] | null = null;
@@ -18,7 +27,7 @@ let _tokenManager: ReturnType['tokenManager'] | null =
function getAuthService() {
if (!browser) return null;
if (!_authService) {
- const auth = initializeWebAuth({ baseUrl: MANA_AUTH_URL });
+ const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
_authService = auth.authService;
_tokenManager = auth.tokenManager;
}
diff --git a/apps/manacore/apps/web/src/lib/stores/user-settings.svelte.ts b/apps/manacore/apps/web/src/lib/stores/user-settings.svelte.ts
index 6e178a802..7ce7f1600 100644
--- a/apps/manacore/apps/web/src/lib/stores/user-settings.svelte.ts
+++ b/apps/manacore/apps/web/src/lib/stores/user-settings.svelte.ts
@@ -7,14 +7,22 @@
* - localStorage caching for offline support
*/
+import { browser } from '$app/environment';
import { createUserSettingsStore } from '@manacore/shared-theme';
import { authStore } from './auth.svelte';
-// TODO: Use PUBLIC_MANA_CORE_AUTH_URL from env when available
-const MANA_AUTH_URL = 'http://localhost:3001';
+// Get auth URL dynamically at runtime
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return 'http://localhost:3001';
+}
export const userSettings = createUserSettingsStore({
appId: 'manacore',
- authUrl: MANA_AUTH_URL,
+ authUrl: getAuthUrl(),
getAccessToken: () => authStore.getAccessToken(),
});
From 4398fbc29b03bfbb025513e0dcc8a418aeb50c26 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 22:03:12 +0100
Subject: [PATCH 27/34] fix(manacore-web,todo-web): use runtime URLs for
backend API services
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- manacore-web: Update todo, calendar, contacts service files to use
runtime URLs from window instead of import.meta.env (baked at build time)
- manacore-web: Add backend URL env vars to docker-compose.staging.yml
- manacore-web: Fix fallback ports (todo: 3017→3018, calendar: 3014→3016)
- todo-web: Update API client to use runtime URL from window
This enables Docker containers to use runtime-injected URLs instead of
build-time environment variables.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
apps/manacore/apps/web/src/hooks.server.ts | 15 ++++++++
.../apps/web/src/lib/api/services/calendar.ts | 34 +++++++++++++++----
.../apps/web/src/lib/api/services/contacts.ts | 34 +++++++++++++++----
.../apps/web/src/lib/api/services/todo.ts | 34 +++++++++++++++----
apps/todo/apps/web/src/lib/api/client.ts | 22 ++++++++++--
docker-compose.staging.yml | 8 +++++
6 files changed, 123 insertions(+), 24 deletions(-)
diff --git a/apps/manacore/apps/web/src/hooks.server.ts b/apps/manacore/apps/web/src/hooks.server.ts
index ba1b0b200..21b618758 100644
--- a/apps/manacore/apps/web/src/hooks.server.ts
+++ b/apps/manacore/apps/web/src/hooks.server.ts
@@ -8,14 +8,29 @@ import type { Handle } from '@sveltejs/kit';
* but Docker containers need runtime configuration.
*/
+// Auth URL
const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+// Backend URLs for dashboard widgets
+const PUBLIC_TODO_API_URL_CLIENT =
+ process.env.PUBLIC_TODO_API_URL_CLIENT || process.env.PUBLIC_TODO_API_URL || '';
+const PUBLIC_CALENDAR_API_URL_CLIENT =
+ process.env.PUBLIC_CALENDAR_API_URL_CLIENT || process.env.PUBLIC_CALENDAR_API_URL || '';
+const PUBLIC_CLOCK_API_URL_CLIENT =
+ process.env.PUBLIC_CLOCK_API_URL_CLIENT || process.env.PUBLIC_CLOCK_API_URL || '';
+const PUBLIC_CONTACTS_API_URL_CLIENT =
+ process.env.PUBLIC_CONTACTS_API_URL_CLIENT || process.env.PUBLIC_CONTACTS_API_URL || '';
+
export const handle: Handle = async ({ event, resolve }) => {
return resolve(event, {
transformPageChunk: ({ html }) => {
const envScript = ``;
return html.replace('', `${envScript}`);
},
diff --git a/apps/manacore/apps/web/src/lib/api/services/calendar.ts b/apps/manacore/apps/web/src/lib/api/services/calendar.ts
index cefced7d4..444b6ef2d 100644
--- a/apps/manacore/apps/web/src/lib/api/services/calendar.ts
+++ b/apps/manacore/apps/web/src/lib/api/services/calendar.ts
@@ -4,12 +4,32 @@
* Fetches events from the Calendar backend for dashboard widgets.
*/
+import { browser } from '$app/environment';
import { createApiClient, type ApiResult } from '../base-client';
-// Backend URL - falls back to localhost for development
-const CALENDAR_API_URL = import.meta.env.PUBLIC_CALENDAR_API_URL || 'http://localhost:3014/api/v1';
+// Get Calendar API URL dynamically at runtime
+function getCalendarApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ const injectedUrl = (window as unknown as { __PUBLIC_CALENDAR_API_URL__?: string })
+ .__PUBLIC_CALENDAR_API_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ // Fallback for local development
+ return 'http://localhost:3016/api/v1';
+}
-const client = createApiClient(CALENDAR_API_URL);
+// Lazy-initialized client to ensure we get the correct URL at runtime
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getCalendarApiUrl());
+ }
+ return _client;
+}
/**
* Calendar entity from Calendar backend
@@ -59,7 +79,7 @@ export const calendarService = {
const startDate = new Date().toISOString().split('T')[0];
const endDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
- const result = await client.get<{ events: CalendarEvent[] }>(
+ const result = await getClient().get<{ events: CalendarEvent[] }>(
`/events?startDate=${startDate}&endDate=${endDate}`
);
@@ -75,7 +95,7 @@ export const calendarService = {
*/
async getTodayEvents(): Promise> {
const today = new Date().toISOString().split('T')[0];
- const result = await client.get<{ events: CalendarEvent[] }>(
+ const result = await getClient().get<{ events: CalendarEvent[] }>(
`/events?startDate=${today}&endDate=${today}`
);
@@ -90,7 +110,7 @@ export const calendarService = {
* Get all calendars
*/
async getCalendars(): Promise> {
- const result = await client.get<{ calendars: Calendar[] }>('/calendars');
+ const result = await getClient().get<{ calendars: Calendar[] }>('/calendars');
if (result.error || !result.data) {
return { data: null, error: result.error };
@@ -109,7 +129,7 @@ export const calendarService = {
const startDate = new Date().toISOString().split('T')[0];
const endDate = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
- const result = await client.get<{ events: CalendarEvent[] }>(
+ const result = await getClient().get<{ events: CalendarEvent[] }>(
`/events?calendarIds=${calendarId}&startDate=${startDate}&endDate=${endDate}`
);
diff --git a/apps/manacore/apps/web/src/lib/api/services/contacts.ts b/apps/manacore/apps/web/src/lib/api/services/contacts.ts
index 2491e8427..b2aa226f3 100644
--- a/apps/manacore/apps/web/src/lib/api/services/contacts.ts
+++ b/apps/manacore/apps/web/src/lib/api/services/contacts.ts
@@ -4,12 +4,32 @@
* Fetches contacts from the Contacts backend for dashboard widgets.
*/
+import { browser } from '$app/environment';
import { createApiClient, type ApiResult } from '../base-client';
-// Backend URL - falls back to localhost for development
-const CONTACTS_API_URL = import.meta.env.PUBLIC_CONTACTS_API_URL || 'http://localhost:3015/api/v1';
+// Get Contacts API URL dynamically at runtime
+function getContactsApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ const injectedUrl = (window as unknown as { __PUBLIC_CONTACTS_API_URL__?: string })
+ .__PUBLIC_CONTACTS_API_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ // Fallback for local development
+ return 'http://localhost:3015/api/v1';
+}
-const client = createApiClient(CONTACTS_API_URL);
+// Lazy-initialized client to ensure we get the correct URL at runtime
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getContactsApiUrl());
+ }
+ return _client;
+}
/**
* Contact entity from Contacts backend
@@ -55,7 +75,7 @@ export const contactsService = {
* Get favorite contacts
*/
async getFavoriteContacts(limit: number = 5): Promise> {
- const result = await client.get(`/contacts?isFavorite=true&limit=${limit}`);
+ const result = await getClient().get(`/contacts?isFavorite=true&limit=${limit}`);
return result;
},
@@ -63,7 +83,7 @@ export const contactsService = {
* Get recent contacts (by updatedAt)
*/
async getRecentContacts(limit: number = 5): Promise> {
- const result = await client.get(`/contacts?limit=${limit}`);
+ const result = await getClient().get(`/contacts?limit=${limit}`);
if (result.error || !result.data) {
return result;
@@ -82,7 +102,7 @@ export const contactsService = {
* Get contacts with upcoming birthdays
*/
async getUpcomingBirthdays(days: number = 30): Promise> {
- const result = await client.get('/contacts');
+ const result = await getClient().get('/contacts');
if (result.error || !result.data) {
return result;
@@ -113,7 +133,7 @@ export const contactsService = {
* Get contact count
*/
async getContactCount(): Promise> {
- const result = await client.get('/contacts');
+ const result = await getClient().get('/contacts');
if (result.error || !result.data) {
return { data: null, error: result.error };
diff --git a/apps/manacore/apps/web/src/lib/api/services/todo.ts b/apps/manacore/apps/web/src/lib/api/services/todo.ts
index 8ab3f6bc6..16389b036 100644
--- a/apps/manacore/apps/web/src/lib/api/services/todo.ts
+++ b/apps/manacore/apps/web/src/lib/api/services/todo.ts
@@ -4,12 +4,32 @@
* Fetches tasks from the Todo backend for dashboard widgets.
*/
+import { browser } from '$app/environment';
import { createApiClient, type ApiResult } from '../base-client';
-// Backend URL - falls back to localhost for development
-const TODO_API_URL = import.meta.env.PUBLIC_TODO_API_URL || 'http://localhost:3017/api/v1';
+// Get Todo API URL dynamically at runtime
+function getTodoApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ // Client-side: use injected window variable (set by hooks.server.ts)
+ const injectedUrl = (window as unknown as { __PUBLIC_TODO_API_URL__?: string })
+ .__PUBLIC_TODO_API_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ // Fallback for local development
+ return 'http://localhost:3018/api/v1';
+}
-const client = createApiClient(TODO_API_URL);
+// Lazy-initialized client to ensure we get the correct URL at runtime
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getTodoApiUrl());
+ }
+ return _client;
+}
/**
* Task entity from Todo backend
@@ -49,7 +69,7 @@ export const todoService = {
* Get today's tasks
*/
async getTodayTasks(): Promise> {
- const result = await client.get<{ tasks: Task[] }>('/tasks/today');
+ const result = await getClient().get<{ tasks: Task[] }>('/tasks/today');
if (result.error || !result.data) {
return { data: null, error: result.error };
@@ -62,7 +82,7 @@ export const todoService = {
* Get upcoming tasks for the next N days
*/
async getUpcomingTasks(days: number = 7): Promise> {
- const result = await client.get<{ tasks: Task[] }>(`/tasks/upcoming?days=${days}`);
+ const result = await getClient().get<{ tasks: Task[] }>(`/tasks/upcoming?days=${days}`);
if (result.error || !result.data) {
return { data: null, error: result.error };
@@ -75,7 +95,7 @@ export const todoService = {
* Get inbox tasks (unassigned to project)
*/
async getInboxTasks(): Promise> {
- const result = await client.get<{ tasks: Task[] }>('/tasks/inbox');
+ const result = await getClient().get<{ tasks: Task[] }>('/tasks/inbox');
if (result.error || !result.data) {
return { data: null, error: result.error };
@@ -88,7 +108,7 @@ export const todoService = {
* Get all projects
*/
async getProjects(): Promise> {
- const result = await client.get<{ projects: Project[] }>('/projects');
+ const result = await getClient().get<{ projects: Project[] }>('/projects');
if (result.error || !result.data) {
return { data: null, error: result.error };
diff --git a/apps/todo/apps/web/src/lib/api/client.ts b/apps/todo/apps/web/src/lib/api/client.ts
index 606a54945..eb4e6ed00 100644
--- a/apps/todo/apps/web/src/lib/api/client.ts
+++ b/apps/todo/apps/web/src/lib/api/client.ts
@@ -12,12 +12,28 @@ interface ApiError {
statusCode: number;
}
+/**
+ * Get the backend URL, preferring runtime-injected value in browser
+ * This allows Docker to inject PUBLIC_BACKEND_URL_CLIENT at runtime
+ * instead of using the build-time PUBLIC_BACKEND_URL
+ */
+function getBackendUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const runtimeUrl = (window as Window & { __PUBLIC_BACKEND_URL__?: string })
+ .__PUBLIC_BACKEND_URL__;
+ if (runtimeUrl) {
+ return runtimeUrl;
+ }
+ }
+ return PUBLIC_BACKEND_URL || 'http://localhost:3018';
+}
+
class ApiClient {
- private baseUrl: string;
private accessToken: string | null = null;
- constructor() {
- this.baseUrl = PUBLIC_BACKEND_URL || 'http://localhost:3018';
+ // Use getter to evaluate URL at request time (browser may hydrate after construction)
+ private get baseUrl(): string {
+ return getBackendUrl();
}
setAccessToken(token: string | null) {
diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml
index 7e0d1940a..b33ba737c 100644
--- a/docker-compose.staging.yml
+++ b/docker-compose.staging.yml
@@ -173,8 +173,16 @@ services:
environment:
NODE_ENV: staging
PORT: 5173
+ # Auth URLs
PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ # Backend URLs for dashboard widgets
+ PUBLIC_TODO_API_URL: http://todo-backend:3018
+ PUBLIC_TODO_API_URL_CLIENT: http://46.224.108.214:3018
+ PUBLIC_CALENDAR_API_URL: http://calendar-backend:3016
+ PUBLIC_CALENDAR_API_URL_CLIENT: http://46.224.108.214:3016
+ PUBLIC_CLOCK_API_URL: http://clock-backend:3017
+ PUBLIC_CLOCK_API_URL_CLIENT: http://46.224.108.214:3017
ports:
- "5173:5173"
healthcheck:
From a50e4e5026013af2a0747e0704be701850c35af5 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 22:27:34 +0100
Subject: [PATCH 28/34] fix(staging): add manacore-web origin to backend CORS
configs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Allow manacore-web (port 5173) to call todo-backend, calendar-backend,
and clock-backend APIs by adding it to their CORS_ORIGINS.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
docker-compose.staging.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml
index b33ba737c..e8741a4bf 100644
--- a/docker-compose.staging.yml
+++ b/docker-compose.staging.yml
@@ -220,7 +220,7 @@ services:
DB_PORT: 5432
DB_USER: ${POSTGRES_USER:-postgres}
MANA_CORE_AUTH_URL: http://mana-core-auth:3001
- CORS_ORIGINS: http://46.224.108.214:5188,http://localhost:5188
+ CORS_ORIGINS: http://46.224.108.214:5188,http://46.224.108.214:5173,http://localhost:5188,http://localhost:5173
ports:
- "3018:3018"
healthcheck:
@@ -288,7 +288,7 @@ services:
DB_PORT: 5432
DB_USER: ${POSTGRES_USER:-postgres}
MANA_CORE_AUTH_URL: http://mana-core-auth:3001
- CORS_ORIGINS: http://46.224.108.214:5186,http://localhost:5186
+ CORS_ORIGINS: http://46.224.108.214:5186,http://46.224.108.214:5173,http://localhost:5186,http://localhost:5173
ports:
- "3016:3016"
healthcheck:
@@ -356,7 +356,7 @@ services:
DB_PORT: 5432
DB_USER: ${POSTGRES_USER:-postgres}
MANA_CORE_AUTH_URL: http://mana-core-auth:3001
- CORS_ORIGINS: http://46.224.108.214:5187,http://localhost:5187
+ CORS_ORIGINS: http://46.224.108.214:5187,http://46.224.108.214:5173,http://localhost:5187,http://localhost:5173
ports:
- "3017:3017"
healthcheck:
From 66bc3e95865c6975d62843f2a8175c634c37aa5c Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 22:43:53 +0100
Subject: [PATCH 29/34] docs: add staging deployment issues troubleshooting
guide
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Documents common issues encountered during staging deployments:
- Runtime env injection for SvelteKit (import.meta.env vs window)
- CORS configuration for cross-app API calls
- CD workflow version tag handling
- Database creation for new backends
- Better Auth user ID format (text vs uuid)
Includes quick debugging commands and port reference.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
docs/STAGING_DEPLOYMENT_ISSUES.md | 267 ++++++++++++++++++++++++++++++
1 file changed, 267 insertions(+)
create mode 100644 docs/STAGING_DEPLOYMENT_ISSUES.md
diff --git a/docs/STAGING_DEPLOYMENT_ISSUES.md b/docs/STAGING_DEPLOYMENT_ISSUES.md
new file mode 100644
index 000000000..2a72310d6
--- /dev/null
+++ b/docs/STAGING_DEPLOYMENT_ISSUES.md
@@ -0,0 +1,267 @@
+# Staging Deployment Issues & Solutions
+
+This document captures common issues encountered during staging deployments and their solutions. Reference this when debugging deployment problems.
+
+## Table of Contents
+
+1. [Runtime Environment Variables (SvelteKit)](#1-runtime-environment-variables-sveltekit)
+2. [CORS Configuration](#2-cors-configuration)
+3. [CD Workflow Version Tags](#3-cd-workflow-version-tags)
+4. [Database Setup](#4-database-setup)
+5. [User ID Format (Better Auth)](#5-user-id-format-better-auth)
+
+---
+
+## 1. Runtime Environment Variables (SvelteKit)
+
+### Problem
+
+SvelteKit apps use `import.meta.env.PUBLIC_*` which gets **baked in at build time**. When running in Docker, the container uses whatever values were present during the GitHub Actions build, not the runtime environment variables.
+
+**Symptoms:**
+- Web apps calling `localhost:3001` instead of staging server IP
+- API calls going to wrong URLs despite correct Docker env vars
+
+### Solution
+
+Use **runtime env injection** via `hooks.server.ts`:
+
+```typescript
+// src/hooks.server.ts
+import type { Handle } from '@sveltejs/kit';
+
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
+```
+
+Then in client code, read from `window` instead of `import.meta.env`:
+
+```typescript
+import { browser } from '$app/environment';
+
+function getApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as any).__PUBLIC_BACKEND_URL__;
+ if (injectedUrl) return injectedUrl;
+ }
+ return 'http://localhost:3000'; // fallback for local dev
+}
+```
+
+### Docker Compose Pattern
+
+Use two environment variables:
+- `PUBLIC_*_URL` - Internal Docker network URL (container-to-container)
+- `PUBLIC_*_URL_CLIENT` - External URL for browser access
+
+```yaml
+environment:
+ PUBLIC_BACKEND_URL: http://backend-container:3000 # Server-side
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:3000 # Browser-side
+```
+
+---
+
+## 2. CORS Configuration
+
+### Problem
+
+Backends only allow CORS from their own web apps, blocking requests from other origins like manacore-web dashboard.
+
+**Symptoms:**
+- `Access to fetch blocked by CORS policy`
+- `No 'Access-Control-Allow-Origin' header`
+
+### Solution
+
+Add all necessary origins to `CORS_ORIGINS` in docker-compose.staging.yml:
+
+```yaml
+todo-backend:
+ environment:
+ # Include both the app's own web AND manacore-web dashboard
+ CORS_ORIGINS: http://46.224.108.214:5188,http://46.224.108.214:5173,http://localhost:5188,http://localhost:5173
+```
+
+### Checklist for New Backends
+
+When deploying a new backend that will be called from manacore-web dashboard:
+1. Add `http://46.224.108.214:5173` to CORS_ORIGINS
+2. Add `http://localhost:5173` for local development
+3. Restart the container after config changes
+
+### Testing CORS
+
+```bash
+curl -I -X OPTIONS http://46.224.108.214:3018/api/v1/endpoint \
+ -H "Origin: http://46.224.108.214:5173" \
+ -H "Access-Control-Request-Method: GET"
+
+# Should see:
+# Access-Control-Allow-Origin: http://46.224.108.214:5173
+```
+
+---
+
+## 3. CD Workflow Version Tags
+
+### Problem
+
+docker-compose uses variables like `${TODO_WEB_VERSION:-latest}`, but the CD workflow wasn't updating the `.env` file on the staging server, causing containers to always use `latest` instead of the tagged version.
+
+**Symptoms:**
+- Deployed new version but container still running old code
+- `docker ps` shows wrong image tag
+
+### Solution
+
+The CD workflow (`.github/workflows/cd-staging-tagged.yml`) now:
+1. Computes the version variable name (e.g., `TODO_WEB_VERSION`)
+2. Updates the `.env` file on staging server
+3. docker-compose reads from `.env`
+
+### Verifying Deployment
+
+```bash
+# Check running container version
+docker ps --format '{{.Names}}: {{.Image}}' | grep todo
+
+# Check .env file
+cat ~/manacore-staging/.env | grep VERSION
+```
+
+---
+
+## 4. Database Setup
+
+### Problem
+
+New backends fail with `database "X" does not exist` because the PostgreSQL databases weren't created.
+
+**Symptoms:**
+- 500 Internal Server Error
+- Logs show: `PostgresError: database "todo" does not exist`
+
+### Solution
+
+Create databases manually on first deployment:
+
+```bash
+# SSH to staging
+ssh deploy@46.224.108.214
+
+# Create databases
+docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE todo;'
+docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE calendar;'
+docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE clock;'
+
+# Restart backends (they auto-migrate schemas on startup)
+cd ~/manacore-staging
+docker compose restart todo-backend calendar-backend clock-backend
+```
+
+### Checklist for New Apps
+
+When deploying a new app with a database:
+1. Create the database: `CREATE DATABASE appname;`
+2. The backend will auto-migrate the schema on startup
+3. Verify tables exist: `\dt` in psql
+
+---
+
+## 5. User ID Format (Better Auth)
+
+### Problem
+
+Backend database schemas use `uuid` type for `user_id`, but Better Auth generates non-UUID user IDs like `otUe1YrfENPdHnrF3g1vSBfpkQfambCZ`.
+
+**Symptoms:**
+- 500 Internal Server Error on authenticated requests
+- Logs show: `invalid input syntax for type uuid: "otUe1YrfENPdHnrF3g1vSBfpkQfambCZ"`
+
+### Solution
+
+Change `user_id` columns from `uuid` to `text`:
+
+```sql
+-- For each table with user_id
+ALTER TABLE tasks ALTER COLUMN user_id TYPE text;
+ALTER TABLE projects ALTER COLUMN user_id TYPE text;
+-- etc.
+```
+
+### Prevention
+
+When creating new backend schemas, **always use `text` type for user_id**:
+
+```typescript
+// Drizzle schema - CORRECT
+export const tasks = pgTable('tasks', {
+ id: uuid('id').defaultRandom().primaryKey(),
+ userId: text('user_id').notNull(), // Use text, not uuid
+ // ...
+});
+
+// WRONG - Don't do this
+export const tasks = pgTable('tasks', {
+ userId: uuid('user_id').notNull(), // Will fail with Better Auth
+});
+```
+
+---
+
+## Quick Debugging Commands
+
+```bash
+# Check container logs
+docker logs --tail 50
+
+# Check container is running correct version
+docker ps --format '{{.Names}}: {{.Image}}'
+
+# Test CORS
+curl -I -X OPTIONS -H "Origin: "
+
+# Check database exists
+docker exec manacore-postgres-staging psql -U postgres -c '\l'
+
+# Check tables in database
+docker exec manacore-postgres-staging psql -U postgres -d -c '\dt'
+
+# Restart a service
+cd ~/manacore-staging && docker compose restart
+
+# Force recreate with new config
+cd ~/manacore-staging && docker compose up -d --no-deps --force-recreate
+```
+
+---
+
+## Port Reference
+
+| Service | Port |
+|---------|------|
+| mana-core-auth | 3001 |
+| chat-backend | 3002 |
+| calendar-backend | 3016 |
+| clock-backend | 3017 |
+| todo-backend | 3018 |
+| chat-web | 3000 |
+| manacore-web | 5173 |
+| calendar-web | 5186 |
+| clock-web | 5187 |
+| todo-web | 5188 |
From 6db875355c6a4b3a981212782582751adb0b979b Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 22:52:59 +0100
Subject: [PATCH 30/34] debug(auth): add detailed logging to JwtAuthGuard
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add console.log statements to JwtAuthGuard to diagnose
401 errors on /api/v1/settings endpoint.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
.../src/common/guards/jwt-auth.guard.ts | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
index bd83c130d..27724df9f 100644
--- a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
+++ b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts
@@ -5,7 +5,7 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
-import { jwtVerify, createRemoteJWKSet, type JWTPayload } from 'jose';
+import { jwtVerify, createRemoteJWKSet } from 'jose';
/**
* JWT Auth Guard using JWKS (Better Auth compatible)
@@ -23,7 +23,10 @@ export class JwtAuthGuard implements CanActivate {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
+ console.log('[JwtAuthGuard] Token (first 50 chars):', token?.substring(0, 50));
+
if (!token) {
+ console.log('[JwtAuthGuard] No token provided');
throw new UnauthorizedException('No token provided');
}
@@ -32,17 +35,22 @@ export class JwtAuthGuard implements CanActivate {
if (!this.jwks) {
const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001';
const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl);
+ console.log('[JwtAuthGuard] Initializing JWKS from:', jwksUrl.toString());
this.jwks = createRemoteJWKSet(jwksUrl);
}
const issuer = this.configService.get('jwt.issuer') || 'manacore';
const audience = this.configService.get('jwt.audience') || 'manacore';
+ console.log('[JwtAuthGuard] Verifying with issuer:', issuer, 'audience:', audience);
+
const { payload } = await jwtVerify(token, this.jwks, {
issuer,
audience,
});
+ console.log('[JwtAuthGuard] Verification SUCCESS, user:', payload.sub);
+
// Attach user to request
request.user = {
userId: payload.sub,
@@ -52,7 +60,7 @@ export class JwtAuthGuard implements CanActivate {
return true;
} catch (error) {
- console.debug('[JwtAuthGuard] Token verification failed:', error);
+ console.error('[JwtAuthGuard] Token verification FAILED:', error);
throw new UnauthorizedException('Invalid token');
}
}
From 81a2970e6cf8be614941f33e47e9e2b8c04312c0 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 23:02:40 +0100
Subject: [PATCH 31/34] docs: add tag naming convention for mana-core-auth
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add documentation explaining the correct tag format for mana-core-auth
deployments. Using `auth-staging-*` instead of `mana-core-auth-staging-*`
causes the CD workflow to fail.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
docs/STAGING_DEPLOYMENT_ISSUES.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/docs/STAGING_DEPLOYMENT_ISSUES.md b/docs/STAGING_DEPLOYMENT_ISSUES.md
index 2a72310d6..1dd0e283d 100644
--- a/docs/STAGING_DEPLOYMENT_ISSUES.md
+++ b/docs/STAGING_DEPLOYMENT_ISSUES.md
@@ -134,6 +134,18 @@ The CD workflow (`.github/workflows/cd-staging-tagged.yml`) now:
2. Updates the `.env` file on staging server
3. docker-compose reads from `.env`
+### Tag Naming Convention
+
+Tags must follow the exact project name as defined in the CD workflow:
+
+| Project | Correct Tag Format | Wrong Format |
+|---------|-------------------|--------------|
+| mana-core-auth | `mana-core-auth-staging-v1.0.0` | `auth-staging-v1.0.0` |
+| chat | `chat-staging-v1.0.0` or `chat-all-staging-v1.0.0` | - |
+| todo | `todo-staging-v1.0.0` or `todo-all-staging-v1.0.0` | - |
+
+**Note**: Using the wrong tag format (e.g., `auth-staging-*` instead of `mana-core-auth-staging-*`) will cause the workflow to fail because it won't find the correct Dockerfile path.
+
### Verifying Deployment
```bash
From d074e6d2e512a293599dbe5144032f2a4d6b31c4 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Mon, 8 Dec 2025 23:11:01 +0100
Subject: [PATCH 32/34] docs: comprehensive staging deployment troubleshooting
guide
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add extensive documentation for staging deployment issues:
- Lazy client initialization pattern for runtime URLs
- PostgreSQL ALTER TABLE USING clause requirement
- Debugging checklist for common issues (API, CORS, 500, 401)
- Summary table of common mistakes and prevention
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
docs/STAGING_DEPLOYMENT_ISSUES.md | 135 +++++++++++++++++++++++++++++-
1 file changed, 132 insertions(+), 3 deletions(-)
diff --git a/docs/STAGING_DEPLOYMENT_ISSUES.md b/docs/STAGING_DEPLOYMENT_ISSUES.md
index 1dd0e283d..4df93e2dd 100644
--- a/docs/STAGING_DEPLOYMENT_ISSUES.md
+++ b/docs/STAGING_DEPLOYMENT_ISSUES.md
@@ -9,6 +9,8 @@ This document captures common issues encountered during staging deployments and
3. [CD Workflow Version Tags](#3-cd-workflow-version-tags)
4. [Database Setup](#4-database-setup)
5. [User ID Format (Better Auth)](#5-user-id-format-better-auth)
+6. [Debugging Checklist](#6-debugging-checklist)
+7. [Summary: Common Mistakes to Avoid](#summary-common-mistakes-to-avoid)
---
@@ -62,6 +64,37 @@ function getApiUrl(): string {
}
```
+### Lazy Client Initialization Pattern
+
+**Important**: API clients must be lazily initialized to read the URL at request time, not at module load time:
+
+```typescript
+// CORRECT - Lazy initialization
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getApiUrl()); // URL evaluated when called
+ }
+ return _client;
+}
+
+export async function getTasks() {
+ return getClient().get('/tasks'); // Client created on first use
+}
+```
+
+```typescript
+// WRONG - Module-level initialization
+const client = createApiClient(getApiUrl()); // URL evaluated at import time!
+
+export async function getTasks() {
+ return client.get('/tasks'); // Will use stale URL
+}
+```
+
+**Why this matters**: When the module is imported, the `window` object may not have the injected environment variables yet. The lazy pattern ensures the URL is read only when the client is actually needed.
+
### Docker Compose Pattern
Use two environment variables:
@@ -210,12 +243,22 @@ Backend database schemas use `uuid` type for `user_id`, but Better Auth generate
Change `user_id` columns from `uuid` to `text`:
```sql
--- For each table with user_id
-ALTER TABLE tasks ALTER COLUMN user_id TYPE text;
-ALTER TABLE projects ALTER COLUMN user_id TYPE text;
+-- For each table with user_id (use USING clause for explicit conversion)
+ALTER TABLE tasks ALTER COLUMN user_id TYPE text USING user_id::text;
+ALTER TABLE projects ALTER COLUMN user_id TYPE text USING user_id::text;
-- etc.
```
+**Important**: Always use the `USING` clause when converting column types. Without it, PostgreSQL may silently fail or produce unexpected results:
+
+```sql
+-- CORRECT - Explicit conversion
+ALTER TABLE events ALTER COLUMN user_id TYPE text USING user_id::text;
+
+-- RISKY - May fail silently on some data types
+ALTER TABLE events ALTER COLUMN user_id TYPE text;
+```
+
### Prevention
When creating new backend schemas, **always use `text` type for user_id**:
@@ -277,3 +320,89 @@ cd ~/manacore-staging && docker compose up -d --no-deps --force-recreate /api/v1/endpoint \
+ -H "Origin: http://46.224.108.214:5173"
+ ```
+ Should return `Access-Control-Allow-Origin` header.
+
+3. **Check container logs**
+ ```bash
+ ssh deploy@46.224.108.214 "docker logs --tail 100"
+ ```
+
+### 500 Internal Server Error
+
+1. **Check database exists**
+ ```bash
+ docker exec manacore-postgres-staging psql -U postgres -c '\l'
+ ```
+
+2. **Check tables exist**
+ ```bash
+ docker exec manacore-postgres-staging psql -U postgres -d -c '\dt'
+ ```
+
+3. **Check for type mismatches** (especially user_id uuid vs text)
+
+### 401 Unauthorized
+
+1. **Check token is being sent**
+ ```bash
+ # In browser Network tab, check Authorization header
+ ```
+
+2. **Check JWKS endpoint**
+ ```bash
+ curl http://46.224.108.214:3001/api/v1/auth/jwks
+ ```
+
+3. **Check issuer/audience match** - Token must have `iss: manacore` and `aud: manacore`
+
+### Container Not Updated
+
+1. **Check image version**
+ ```bash
+ docker ps --format '{{.Names}}: {{.Image}}'
+ ```
+
+2. **Check .env file**
+ ```bash
+ cat ~/manacore-staging/.env | grep VERSION
+ ```
+
+3. **Force recreate**
+ ```bash
+ docker compose up -d --no-deps --force-recreate
+ ```
+
+---
+
+## Summary: Common Mistakes to Avoid
+
+| Mistake | Consequence | Prevention |
+|---------|-------------|------------|
+| Using `import.meta.env` for Docker runtime | URLs baked at build time | Use `window.__PUBLIC_*__` with runtime injection |
+| Initializing API clients at module level | Client uses stale URLs | Use lazy initialization pattern |
+| Using `uuid` type for user_id | Better Auth IDs fail validation | Always use `text` type for user_id |
+| Missing CORS origin for manacore-web | Dashboard can't call backends | Add port 5173 to all backend CORS configs |
+| Wrong tag format for mana-core-auth | Deployment fails, can't find Dockerfile | Use `mana-core-auth-staging-v*` not `auth-staging-v*` |
+| Forgetting to create database | Backend crashes on startup | Create database before first deployment |
+| ALTER TABLE without USING clause | Silent failures on type conversion | Always use `USING column::new_type` |
From 18a7b2d9a0e85350d2540afd3940004981a035d7 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Tue, 9 Dec 2025 01:00:02 +0100
Subject: [PATCH 33/34] docs: add setup templates and checklists for recurring
tasks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Create SETUP_TEMPLATES.md with copy-paste templates for:
- New SvelteKit web apps (hooks.server.ts, getAuthUrl pattern, Dockerfile)
- New NestJS backends (schema, health, CORS)
- Staging deployments (database creation, tag formats)
- Adding backends to ManaCore dashboard
- Port assignment reference
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude
---
docs/SETUP_TEMPLATES.md | 450 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 450 insertions(+)
create mode 100644 docs/SETUP_TEMPLATES.md
diff --git a/docs/SETUP_TEMPLATES.md b/docs/SETUP_TEMPLATES.md
new file mode 100644
index 000000000..d8cc2fe86
--- /dev/null
+++ b/docs/SETUP_TEMPLATES.md
@@ -0,0 +1,450 @@
+# Setup Templates & Checklists
+
+Quick-reference templates for recurring setup tasks. Copy and customize for new projects.
+
+## Table of Contents
+
+1. [New SvelteKit Web App](#1-new-sveltekit-web-app)
+2. [New NestJS Backend](#2-new-nestjs-backend)
+3. [Deploying New Service to Staging](#3-deploying-new-service-to-staging)
+4. [Adding Backend to ManaCore Dashboard](#4-adding-backend-to-manacore-dashboard)
+5. [Quick Reference Port Assignments](#5-quick-reference-port-assignments)
+
+---
+
+## 1. New SvelteKit Web App
+
+### Checklist
+
+- [ ] Create `src/hooks.server.ts` with runtime env injection
+- [ ] Update auth store to use `getAuthUrl()` pattern
+- [ ] Update user-settings store to use `getAuthUrl()` pattern
+- [ ] Update any API services to use lazy client initialization
+- [ ] Add Dockerfile with pnpm symlink preservation
+- [ ] Add to `docker-compose.staging.yml` with both internal and client URLs
+- [ ] Test locally with `pnpm dev`
+- [ ] Deploy and verify `window.__PUBLIC_*__` variables in browser console
+
+### Template: hooks.server.ts
+
+```typescript
+// src/hooks.server.ts
+import type { Handle } from '@sveltejs/kit';
+
+// Runtime environment variables for client-side injection
+const PUBLIC_MANA_CORE_AUTH_URL_CLIENT =
+ process.env.PUBLIC_MANA_CORE_AUTH_URL_CLIENT || process.env.PUBLIC_MANA_CORE_AUTH_URL || '';
+const PUBLIC_BACKEND_URL_CLIENT =
+ process.env.PUBLIC_BACKEND_URL_CLIENT || process.env.PUBLIC_BACKEND_URL || '';
+
+export const handle: Handle = async ({ event, resolve }) => {
+ return resolve(event, {
+ transformPageChunk: ({ html }) => {
+ const envScript = ``;
+ return html.replace('', `${envScript}`);
+ },
+ });
+};
+```
+
+### Template: getAuthUrl() Pattern
+
+```typescript
+// src/lib/stores/auth.svelte.ts
+import { browser } from '$app/environment';
+
+function getAuthUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MANA_CORE_AUTH_URL__?: string })
+ .__PUBLIC_MANA_CORE_AUTH_URL__;
+ return injectedUrl || 'http://localhost:3001';
+ }
+ return process.env.PUBLIC_MANA_CORE_AUTH_URL || 'http://localhost:3001';
+}
+
+// Usage
+const auth = initializeWebAuth({ baseUrl: getAuthUrl() });
+```
+
+### Template: Lazy API Client Initialization
+
+```typescript
+// src/lib/api/services/myservice.ts
+import { browser } from '$app/environment';
+import { createApiClient } from '../base-client';
+
+function getApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_BACKEND_URL__?: string })
+ .__PUBLIC_BACKEND_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ return 'http://localhost:3000/api/v1';
+}
+
+// IMPORTANT: Lazy initialization - don't create client at module level!
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getApiUrl());
+ }
+ return _client;
+}
+
+export async function getData() {
+ return getClient().get('/data');
+}
+```
+
+### Template: Dockerfile (SvelteKit + pnpm)
+
+```dockerfile
+# Build stage
+FROM node:20-alpine AS builder
+
+RUN npm install -g pnpm@9.15.0
+
+WORKDIR /app
+
+# Copy workspace files
+COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
+COPY apps/MYPROJECT/apps/web/package.json apps/MYPROJECT/apps/web/
+COPY packages/ packages/
+
+# Install all dependencies
+RUN pnpm install --frozen-lockfile
+
+# Copy source and build
+COPY apps/MYPROJECT/apps/web apps/MYPROJECT/apps/web
+RUN pnpm --filter @myproject/web build
+
+# Production stage - PRESERVE PNPM SYMLINK STRUCTURE
+FROM node:20-alpine AS production
+
+# Keep same directory structure as builder
+WORKDIR /app/apps/MYPROJECT/apps/web
+
+# Copy pnpm store (target of symlinks)
+COPY --from=builder /app/node_modules/.pnpm /app/node_modules/.pnpm
+
+# Copy app's node_modules (contains symlinks)
+COPY --from=builder /app/apps/MYPROJECT/apps/web/node_modules ./node_modules
+
+# Copy built app
+COPY --from=builder /app/apps/MYPROJECT/apps/web/build ./build
+COPY --from=builder /app/apps/MYPROJECT/apps/web/package.json ./
+
+EXPOSE 5173
+CMD ["node", "build"]
+```
+
+### Template: docker-compose.staging.yml Entry
+
+```yaml
+myproject-web:
+ image: ghcr.io/memo-2023/myproject-web:${MYPROJECT_WEB_VERSION:-latest}
+ container_name: myproject-web-staging
+ restart: unless-stopped
+ ports:
+ - '51XX:5173'
+ environment:
+ NODE_ENV: production
+ # Server-side URLs (Docker internal network)
+ PUBLIC_BACKEND_URL: http://myproject-backend:30XX
+ PUBLIC_MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ # Client-side URLs (browser access via public IP)
+ PUBLIC_BACKEND_URL_CLIENT: http://46.224.108.214:30XX
+ PUBLIC_MANA_CORE_AUTH_URL_CLIENT: http://46.224.108.214:3001
+ depends_on:
+ myproject-backend:
+ condition: service_healthy
+ healthcheck:
+ test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:5173/health']
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+```
+
+---
+
+## 2. New NestJS Backend
+
+### Checklist
+
+- [ ] Use `text` type for all `user_id` columns (NOT `uuid`)
+- [ ] Add health check endpoint at `/api/v1/health`
+- [ ] Configure CORS to include manacore-web origin (port 5173)
+- [ ] Add database to `docker/init-db/01-create-databases.sql`
+- [ ] Add to `scripts/setup-databases.sh`
+- [ ] Add `dev:myproject:full` command to root `package.json`
+- [ ] Add Dockerfile with correct health check
+- [ ] Add to `docker-compose.staging.yml` with proper CORS config
+
+### Template: Drizzle Schema (user_id as text)
+
+```typescript
+// src/db/schema/main.schema.ts
+import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';
+
+export const items = pgTable('items', {
+ id: uuid('id').defaultRandom().primaryKey(),
+ userId: text('user_id').notNull(), // ALWAYS text, not uuid!
+ title: text('title').notNull(),
+ createdAt: timestamp('created_at').defaultNow().notNull(),
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
+});
+```
+
+### Template: Health Controller
+
+```typescript
+// src/health/health.controller.ts
+import { Controller, Get } from '@nestjs/common';
+
+@Controller('api/v1/health')
+export class HealthController {
+ @Get()
+ check() {
+ return { status: 'ok', timestamp: new Date().toISOString() };
+ }
+}
+```
+
+### Template: CORS Configuration
+
+```typescript
+// src/main.ts
+async function bootstrap() {
+ const app = await NestFactory.create(AppModule);
+
+ const corsOrigins = process.env.CORS_ORIGINS?.split(',') || [
+ 'http://localhost:5173', // Local dev
+ 'http://localhost:51XX', // App's own web
+ ];
+
+ app.enableCors({
+ origin: corsOrigins,
+ credentials: true,
+ });
+
+ await app.listen(process.env.PORT || 30XX);
+}
+```
+
+### Template: docker-compose.staging.yml Entry
+
+```yaml
+myproject-backend:
+ image: ghcr.io/memo-2023/myproject-backend:${MYPROJECT_BACKEND_VERSION:-latest}
+ container_name: myproject-backend-staging
+ restart: unless-stopped
+ ports:
+ - '30XX:30XX'
+ environment:
+ NODE_ENV: production
+ PORT: 30XX
+ DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@postgres:5432/myproject
+ MANA_CORE_AUTH_URL: http://mana-core-auth:3001
+ # CORS - Include app's web AND manacore-web dashboard
+ CORS_ORIGINS: http://46.224.108.214:51XX,http://46.224.108.214:5173,http://localhost:51XX,http://localhost:5173
+ depends_on:
+ postgres:
+ condition: service_healthy
+ mana-core-auth:
+ condition: service_healthy
+ healthcheck:
+ test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:30XX/api/v1/health']
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+ networks:
+ - manacore-network
+```
+
+---
+
+## 3. Deploying New Service to Staging
+
+### Pre-Deployment Checklist
+
+- [ ] Database exists on staging PostgreSQL
+- [ ] Dockerfile has correct health check path (`/api/v1/health` for backends)
+- [ ] `docker-compose.staging.yml` has service definition
+- [ ] CORS_ORIGINS includes all required origins
+- [ ] Environment variables set correctly
+- [ ] Tag format matches project name exactly
+
+### Create Database on Staging
+
+```bash
+ssh -i ~/.ssh/hetzner_deploy_key deploy@46.224.108.214
+
+# Create database
+docker exec manacore-postgres-staging psql -U postgres -c 'CREATE DATABASE myproject;'
+
+# Verify
+docker exec manacore-postgres-staging psql -U postgres -c '\l' | grep myproject
+```
+
+### Deployment Tag Formats
+
+| Project | Correct Tag Format | Wrong Format |
+|---------|-------------------|--------------|
+| mana-core-auth | `mana-core-auth-staging-v1.0.X` | `auth-staging-v1.0.X` |
+| chat | `chat-staging-v1.0.X` or `chat-all-staging-v1.0.X` | - |
+| todo | `todo-staging-v1.0.X` or `todo-all-staging-v1.0.X` | - |
+| calendar | `calendar-staging-v1.0.X` | - |
+| clock | `clock-staging-v1.0.X` | - |
+| myproject | `myproject-staging-v1.0.X` | - |
+
+### Post-Deployment Verification
+
+```bash
+# Check container is running correct version
+docker ps --format '{{.Names}}: {{.Image}}' | grep myproject
+
+# Check health endpoint
+curl http://46.224.108.214:30XX/api/v1/health
+
+# Check logs for errors
+docker logs myproject-backend-staging --tail 50
+
+# Test CORS (from manacore-web origin)
+curl -I -X OPTIONS http://46.224.108.214:30XX/api/v1/endpoint \
+ -H "Origin: http://46.224.108.214:5173" \
+ -H "Access-Control-Request-Method: GET"
+```
+
+---
+
+## 4. Adding Backend to ManaCore Dashboard
+
+When adding a new backend service that manacore-web dashboard should call:
+
+### Checklist
+
+- [ ] Add CORS origin for manacore-web (port 5173) to backend
+- [ ] Create API service file in `manacore/apps/web/src/lib/api/services/`
+- [ ] Add runtime URL injection in `manacore/apps/web/src/hooks.server.ts`
+- [ ] Add environment variables to `docker-compose.staging.yml` for manacore-web
+- [ ] Deploy both manacore-web and the backend with new config
+
+### Template: API Service File
+
+```typescript
+// apps/manacore/apps/web/src/lib/api/services/myservice.ts
+import { browser } from '$app/environment';
+import { createApiClient, type ApiResult } from '../base-client';
+
+function getMyServiceApiUrl(): string {
+ if (browser && typeof window !== 'undefined') {
+ const injectedUrl = (window as unknown as { __PUBLIC_MYSERVICE_API_URL__?: string })
+ .__PUBLIC_MYSERVICE_API_URL__;
+ if (injectedUrl) {
+ return `${injectedUrl}/api/v1`;
+ }
+ }
+ return 'http://localhost:30XX/api/v1';
+}
+
+let _client: ReturnType | null = null;
+
+function getClient() {
+ if (!_client) {
+ _client = createApiClient(getMyServiceApiUrl());
+ }
+ return _client;
+}
+
+// Export API functions
+export async function getItems(): Promise> {
+ return getClient().get('/items');
+}
+
+export async function createItem(data: CreateItemDto): Promise> {
+ return getClient().post('/items', data);
+}
+```
+
+### Template: hooks.server.ts Addition
+
+```typescript
+// Add to existing hooks.server.ts
+const PUBLIC_MYSERVICE_API_URL_CLIENT =
+ process.env.PUBLIC_MYSERVICE_API_URL_CLIENT || process.env.PUBLIC_MYSERVICE_API_URL || '';
+
+// In transformPageChunk, add:
+window.__PUBLIC_MYSERVICE_API_URL__ = "${PUBLIC_MYSERVICE_API_URL_CLIENT}";
+```
+
+### Template: docker-compose.staging.yml Addition
+
+```yaml
+manacore-web:
+ environment:
+ # ... existing env vars ...
+ # Add new backend URL
+ PUBLIC_MYSERVICE_API_URL: http://myservice-backend:30XX
+ PUBLIC_MYSERVICE_API_URL_CLIENT: http://46.224.108.214:30XX
+```
+
+---
+
+## 5. Quick Reference Port Assignments
+
+### Backend Ports (3000-3099)
+
+| Port | Service |
+|------|---------|
+| 3000 | chat-web (legacy) |
+| 3001 | mana-core-auth |
+| 3002 | chat-backend |
+| 3006 | picture-backend |
+| 3007 | zitare-backend |
+| 3009 | manadeck-backend |
+| 3015 | contacts-backend |
+| 3016 | calendar-backend |
+| 3017 | clock-backend |
+| 3018 | todo-backend |
+
+### Web App Ports (5100-5199)
+
+| Port | Service |
+|------|---------|
+| 5173 | manacore-web |
+| 5175 | picture-web |
+| 5177 | zitare-web |
+| 5179 | calendar-web |
+| 5184 | contacts-web |
+| 5186 | calendar-web (staging) |
+| 5187 | clock-web |
+| 5188 | todo-web |
+
+### Next Available Ports
+
+- **Backend**: 3019, 3020, 3021...
+- **Web**: 5189, 5190, 5191...
+
+---
+
+## Common Mistakes Quick Reference
+
+| Mistake | Fix |
+|---------|-----|
+| `import.meta.env` in Docker | Use `window.__PUBLIC_*__` injection |
+| API client at module level | Use lazy `getClient()` pattern |
+| `uuid` type for user_id | Use `text` type |
+| Missing CORS for 5173 | Add manacore-web to CORS_ORIGINS |
+| `auth-staging-v*` tag | Use `mana-core-auth-staging-v*` |
+| ALTER TABLE without USING | Use `USING column::text` |
+| `/api/health` endpoint | Use `/api/v1/health` |
From 8af01724d75eef748d689be7b0871368d9328c10 Mon Sep 17 00:00:00 2001
From: Wuesteon
Date: Tue, 9 Dec 2025 02:13:11 +0100
Subject: [PATCH 34/34] =?UTF-8?q?=E2=9C=A8=20feat(db):=20add=20production-?=
=?UTF-8?q?safe=20migration=20system=20with=20advisory=20locks?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add migrate.ts script with PostgreSQL advisory locks to prevent concurrent migrations
- Add retry logic with exponential backoff for transient connection errors
- Update CI/CD workflows to run migrations before deployment with health polling
- Create comprehensive DATABASE_MIGRATIONS.md documentation covering:
- Drizzle ORM internals (push vs generate/migrate modes)
- Migration tracking (journal files, __drizzle_migrations table)
- Advisory lock architecture and timeout handling
- Zero-downtime migration patterns (expand-contract)
- Troubleshooting guide
- Update .claude/guidelines/database.md with migration quick reference
- Remove stale migration files that caused schema conflicts
---
.claude/guidelines/database.md | 105 +-
.github/workflows/cd-production.yml | 48 +-
.github/workflows/cd-staging.yml | 243 ++-
CLAUDE.md | 1 +
docs/DATABASE_MIGRATIONS.md | 667 ++++++++
services/mana-core-auth/CLAUDE.md | 14 +-
services/mana-core-auth/package.json | 2 +
services/mana-core-auth/src/db/migrate.ts | 222 +++
.../src/db/migrations/0001_zippy_ma_gnuci.sql | 39 -
.../src/db/migrations/meta/0001_snapshot.json | 1501 -----------------
10 files changed, 1146 insertions(+), 1696 deletions(-)
create mode 100644 docs/DATABASE_MIGRATIONS.md
create mode 100644 services/mana-core-auth/src/db/migrate.ts
delete mode 100644 services/mana-core-auth/src/db/migrations/0001_zippy_ma_gnuci.sql
delete mode 100644 services/mana-core-auth/src/db/migrations/meta/0001_snapshot.json
diff --git a/.claude/guidelines/database.md b/.claude/guidelines/database.md
index 49bfbebf4..a503f86cd 100644
--- a/.claude/guidelines/database.md
+++ b/.claude/guidelines/database.md
@@ -349,6 +349,15 @@ async function getPaginated(
## Migrations
+> **Comprehensive Documentation**: See **[docs/DATABASE_MIGRATIONS.md](/docs/DATABASE_MIGRATIONS.md)** for full migration internals, CI/CD integration, zero-downtime patterns, and troubleshooting.
+
+### Quick Reference
+
+| Environment | Command | Purpose |
+| --------------- | ----------------- | ------------------------------- |
+| **Development** | `pnpm db:push` | Fast iteration, direct sync |
+| **Production** | `pnpm db:migrate` | Tracked migrations with history |
+
### Configuration
```typescript
@@ -358,9 +367,9 @@ import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema/index.ts',
out: './src/db/migrations',
- driver: 'pg',
+ dialect: 'postgresql',
dbCredentials: {
- connectionString: process.env.DATABASE_URL!,
+ url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
@@ -370,41 +379,85 @@ export default defineConfig({
### Commands
```bash
-# Generate migration from schema changes
-pnpm drizzle-kit generate
+# Development - push schema directly (fast, no history)
+pnpm db:push
-# Push schema directly (development only)
-pnpm drizzle-kit push
-
-# Open Drizzle Studio
-pnpm drizzle-kit studio
-
-# Run migrations (production)
+# Production - generate and run migrations
+pnpm db:generate --name add_user_preferences
pnpm db:migrate
+
+# Open Drizzle Studio for database inspection
+pnpm db:studio
```
-### Migration Runner
+### Migration Workflow
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Which command should I use? │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ Local development? │
+│ └── YES → pnpm db:push (fast, no tracking) │
+│ │
+│ Staging/Production? │
+│ └── YES → pnpm db:generate + pnpm db:migrate (tracked) │
+│ │
+│ Schema changed by someone else? │
+│ └── YES → git pull + pnpm db:push (local) │
+│ git pull + pnpm db:migrate (staging/prod) │
+│ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+### Key Concepts
+
+1. **Advisory Locks**: Migrations use PostgreSQL advisory locks to prevent concurrent execution
+2. **Migration Tracking**: `__drizzle_migrations` table + `meta/_journal.json` file
+3. **Migrations run BEFORE code deployment**: Ensures database is ready for new code
+4. **Never modify applied migrations**: Create new migrations instead
+5. **Zero-downtime**: Use expand-contract pattern for breaking schema changes
+
+### Production Migration Script
+
+Production backends use a migration script with advisory locks:
```typescript
-// src/db/migrate.ts
-import { drizzle } from 'drizzle-orm/postgres-js';
-import { migrate } from 'drizzle-orm/postgres-js/migrator';
-import postgres from 'postgres';
+// src/db/migrate.ts - Key features:
+// - Advisory lock (pg_try_advisory_lock) prevents concurrent migrations
+// - Retry logic with exponential backoff for transient failures
+// - Timeout protection (default 5 minutes)
+// - Graceful handling when no migrations exist
-async function runMigrations() {
- const connection = postgres(process.env.DATABASE_URL!, { max: 1 });
- const db = drizzle(connection);
+const MIGRATION_LOCK_ID = 987654321; // Unique per service
- console.log('Running migrations...');
- await migrate(db, { migrationsFolder: './src/db/migrations' });
- console.log('Migrations complete');
-
- await connection.end();
+async function acquireLock(db) {
+ const result = await db.execute(
+ sql`SELECT pg_try_advisory_lock(${MIGRATION_LOCK_ID}) as acquired`
+ );
+ return result[0]?.acquired === true;
}
-
-runMigrations().catch(console.error);
```
+See `services/mana-core-auth/src/db/migrate.ts` for the full implementation.
+
+### Best Practices
+
+**DO:**
+
+- Run migrations before deploying new code
+- Test migrations in staging before production
+- Use `CONCURRENTLY` for index creation
+- Keep migrations small and focused
+- Commit migration files to version control
+
+**DON'T:**
+
+- Run `db:push` in production
+- Delete or modify applied migrations
+- Add NOT NULL without default or backfill
+- Drop columns immediately (wait 1-2 weeks)
+
## Query Patterns
### Select with Joins
diff --git a/.github/workflows/cd-production.yml b/.github/workflows/cd-production.yml
index ec614d4bb..564aa0c84 100644
--- a/.github/workflows/cd-production.yml
+++ b/.github/workflows/cd-production.yml
@@ -212,8 +212,52 @@ jobs:
ssh ${{ secrets.PRODUCTION_USER }}@${{ secrets.PRODUCTION_HOST }} << 'EOF'
cd ~/manacore-production
- # Run migrations before deploying new code
- docker compose run --rm mana-core-auth pnpm run db:migrate || echo "Migrations completed or skipped"
+ echo "=== Running Database Migrations ==="
+ echo ""
+
+ # Migration function with retry logic
+ run_migration() {
+ local service=$1
+ local max_attempts=3
+ local timeout=300 # 5 minutes
+ local attempt=1
+
+ while [ $attempt -le $max_attempts ]; do
+ echo "[$service] Migration attempt $attempt/$max_attempts..."
+
+ # Run migration with timeout using a temporary container
+ if timeout $timeout docker compose run --rm $service pnpm run db:migrate 2>&1; then
+ echo "✅ [$service] Migration succeeded"
+ return 0
+ else
+ exit_code=$?
+ if [ $exit_code -eq 124 ]; then
+ echo "⚠️ [$service] Migration timeout after ${timeout}s"
+ else
+ echo "⚠️ [$service] Migration failed with exit code $exit_code"
+ fi
+
+ attempt=$((attempt + 1))
+ if [ $attempt -le $max_attempts ]; then
+ wait_time=$((10 * attempt)) # Backoff: 10s, 20s, 30s
+ echo " Waiting ${wait_time}s before retry..."
+ sleep $wait_time
+ fi
+ fi
+ done
+
+ echo "❌ [$service] Migration failed after $max_attempts attempts"
+ return 1
+ }
+
+ # Run migrations for mana-core-auth (central auth service)
+ run_migration mana-core-auth || {
+ echo "❌ mana-core-auth migration failed"
+ echo "⚠️ Continuing with deployment - manual migration may be required"
+ }
+
+ echo ""
+ echo "✅ Migration step completed"
EOF
- name: Deploy with zero-downtime
diff --git a/.github/workflows/cd-staging.yml b/.github/workflows/cd-staging.yml
index 1852ed42a..4af222aed 100644
--- a/.github/workflows/cd-staging.yml
+++ b/.github/workflows/cd-staging.yml
@@ -203,6 +203,69 @@ jobs:
echo "✅ Databases ready"
EOF
+ - name: Run database migrations
+ env:
+ STAGING_USER: deploy
+ STAGING_HOST: 46.224.108.214
+ run: |
+ ssh $STAGING_USER@$STAGING_HOST << 'EOF'
+ cd ~/manacore-staging
+
+ echo "=== Running Database Migrations ==="
+ echo ""
+
+ # Migration function with retry logic
+ run_migration() {
+ local service=$1
+ local max_attempts=3
+ local timeout=300 # 5 minutes
+ local attempt=1
+
+ while [ $attempt -le $max_attempts ]; do
+ echo "[$service] Migration attempt $attempt/$max_attempts..."
+
+ # Run migration with timeout
+ if timeout $timeout docker compose exec -T $service pnpm run db:migrate 2>&1; then
+ echo "✅ [$service] Migration succeeded"
+ return 0
+ else
+ exit_code=$?
+ if [ $exit_code -eq 124 ]; then
+ echo "⚠️ [$service] Migration timeout after ${timeout}s"
+ else
+ echo "⚠️ [$service] Migration failed with exit code $exit_code"
+ fi
+
+ attempt=$((attempt + 1))
+ if [ $attempt -le $max_attempts ]; then
+ wait_time=$((10 * attempt)) # Backoff: 10s, 20s, 30s
+ echo " Waiting ${wait_time}s before retry..."
+ sleep $wait_time
+ fi
+ fi
+ done
+
+ echo "❌ [$service] Migration failed after $max_attempts attempts"
+ return 1
+ }
+
+ # Run migrations for services that have db:migrate script
+ # mana-core-auth - central auth service
+ if docker compose exec -T mana-core-auth test -f src/db/migrate.ts 2>/dev/null || \
+ docker compose exec -T mana-core-auth pnpm run db:migrate --help 2>/dev/null; then
+ run_migration mana-core-auth || {
+ echo "❌ mana-core-auth migration failed - aborting deployment"
+ exit 1
+ }
+ else
+ echo "⏭️ [mana-core-auth] No db:migrate script, using db:push..."
+ docker compose exec -T mana-core-auth npx drizzle-kit push --force || echo "Auth schema push completed"
+ fi
+
+ echo ""
+ echo "✅ All migrations completed"
+ EOF
+
- name: Run health checks
env:
STAGING_USER: deploy
@@ -211,143 +274,69 @@ jobs:
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
cd ~/manacore-staging
- # Wait for services to fully start
- echo "Waiting 60s for services to fully initialize..."
- sleep 60
+ echo "=== Health Checks with Polling ==="
+ echo ""
+
+ # Health check function with retry polling
+ check_health() {
+ local service=$1
+ local url=$2
+ local max_attempts=24 # 24 * 5s = 2 minutes max wait
+ local attempt=1
+
+ echo "Checking $service..."
+
+ while [ $attempt -le $max_attempts ]; do
+ # Check if container is running
+ if ! docker compose ps $service 2>/dev/null | grep -q "Up"; then
+ if [ $attempt -eq 1 ]; then
+ echo " ⏳ Waiting for container to start..."
+ fi
+ sleep 5
+ attempt=$((attempt + 1))
+ continue
+ fi
+
+ # Check health endpoint
+ if docker compose exec -T $service wget -q -O - $url > /dev/null 2>&1; then
+ echo " ✅ $service is healthy (attempt $attempt)"
+ return 0
+ fi
+
+ if [ $attempt -eq 1 ]; then
+ echo " ⏳ Waiting for $service to become healthy..."
+ fi
+
+ sleep 5
+ attempt=$((attempt + 1))
+ done
+
+ echo " ❌ $service health check failed after $max_attempts attempts"
+ echo " === Recent Logs ==="
+ docker compose logs --tail=50 $service
+ return 1
+ }
echo "=== Container Status ==="
docker compose ps
-
echo ""
- echo "=== Health Checks ==="
- # Check mana-core-auth
- echo "Checking mana-core-auth..."
- if docker compose exec -T mana-core-auth wget -q -O - http://localhost:3001/api/v1/health > /dev/null 2>&1; then
- echo "✅ mana-core-auth is healthy"
- else
- echo "❌ mana-core-auth health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 mana-core-auth
- exit 1
- fi
-
- # Check chat-backend
- echo "Checking chat-backend..."
- if docker compose exec -T chat-backend wget -q -O - http://localhost:3002/api/v1/health > /dev/null 2>&1; then
- echo "✅ chat-backend is healthy"
- else
- echo "❌ chat-backend health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 chat-backend
- exit 1
- fi
-
- # Check chat-web
- echo "Checking chat-web..."
- if docker compose exec -T chat-web wget -q -O - http://localhost:3000/health > /dev/null 2>&1; then
- echo "✅ chat-web is healthy"
- else
- echo "❌ chat-web health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 chat-web
- exit 1
- fi
-
- # Check manacore-web
- echo "Checking manacore-web..."
- if docker compose exec -T manacore-web wget -q -O - http://localhost:5173/health > /dev/null 2>&1; then
- echo "✅ manacore-web is healthy"
- else
- echo "❌ manacore-web health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 manacore-web
- exit 1
- fi
-
- # Check todo-backend
- echo "Checking todo-backend..."
- if docker compose exec -T todo-backend wget -q -O - http://localhost:3018/api/v1/health > /dev/null 2>&1; then
- echo "✅ todo-backend is healthy"
- else
- echo "❌ todo-backend health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 todo-backend
- exit 1
- fi
-
- # Check todo-web
- echo "Checking todo-web..."
- if docker compose exec -T todo-web wget -q -O - http://localhost:5188/health > /dev/null 2>&1; then
- echo "✅ todo-web is healthy"
- else
- echo "❌ todo-web health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 todo-web
- exit 1
- fi
-
- # Check calendar-backend
- echo "Checking calendar-backend..."
- if docker compose exec -T calendar-backend wget -q -O - http://localhost:3016/api/v1/health > /dev/null 2>&1; then
- echo "✅ calendar-backend is healthy"
- else
- echo "❌ calendar-backend health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 calendar-backend
- exit 1
- fi
-
- # Check calendar-web
- echo "Checking calendar-web..."
- if docker compose exec -T calendar-web wget -q -O - http://localhost:5186/health > /dev/null 2>&1; then
- echo "✅ calendar-web is healthy"
- else
- echo "❌ calendar-web health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 calendar-web
- exit 1
- fi
-
- # Check clock-backend
- echo "Checking clock-backend..."
- if docker compose exec -T clock-backend wget -q -O - http://localhost:3017/api/v1/health > /dev/null 2>&1; then
- echo "✅ clock-backend is healthy"
- else
- echo "❌ clock-backend health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 clock-backend
- exit 1
- fi
-
- # Check clock-web
- echo "Checking clock-web..."
- if docker compose exec -T clock-web wget -q -O - http://localhost:5187/health > /dev/null 2>&1; then
- echo "✅ clock-web is healthy"
- else
- echo "❌ clock-web health check failed"
- echo "=== Logs ==="
- docker compose logs --tail=50 clock-web
- exit 1
- fi
+ # Check all services with polling
+ check_health mana-core-auth http://localhost:3001/api/v1/health || exit 1
+ check_health chat-backend http://localhost:3002/api/v1/health || exit 1
+ check_health chat-web http://localhost:3000/health || exit 1
+ check_health manacore-web http://localhost:5173/health || exit 1
+ check_health todo-backend http://localhost:3018/api/v1/health || exit 1
+ check_health todo-web http://localhost:5188/health || exit 1
+ check_health calendar-backend http://localhost:3016/api/v1/health || exit 1
+ check_health calendar-web http://localhost:5186/health || exit 1
+ check_health clock-backend http://localhost:3017/api/v1/health || exit 1
+ check_health clock-web http://localhost:5187/health || exit 1
echo ""
echo "✅ All health checks passed!"
EOF
- - name: Run database migrations
- env:
- STAGING_USER: deploy
- STAGING_HOST: 46.224.108.214
- run: |
- # Run migrations for services that need them
- ssh $STAGING_USER@$STAGING_HOST << 'EOF'
- cd ~/manacore-staging
-
- # Mana Core Auth - push schema using Drizzle (--force skips interactive confirmation)
- docker compose exec -T mana-core-auth npx drizzle-kit push --force || echo "Auth schema push skipped"
- EOF
-
- name: Deployment summary
run: |
echo "## Staging Deployment Summary" >> $GITHUB_STEP_SUMMARY
diff --git a/CLAUDE.md b/CLAUDE.md
index 9ed990542..f925c51e2 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -636,6 +636,7 @@ PORT=...
- **[docs/LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md)** - Database setup and `dev:*:full` commands
- **[docs/ENVIRONMENT_VARIABLES.md](docs/ENVIRONMENT_VARIABLES.md)** - Complete environment setup guide
+- **[docs/DATABASE_MIGRATIONS.md](docs/DATABASE_MIGRATIONS.md)** - Migration best practices, CI/CD, rollback procedures
Each project has its own `CLAUDE.md` with detailed information:
diff --git a/docs/DATABASE_MIGRATIONS.md b/docs/DATABASE_MIGRATIONS.md
new file mode 100644
index 000000000..1f9b262eb
--- /dev/null
+++ b/docs/DATABASE_MIGRATIONS.md
@@ -0,0 +1,667 @@
+# Database Migration Guide
+
+This document describes database migration best practices, procedures, and tooling for the ManaCore monorepo. **This is a core system concept** - all developers should understand these patterns.
+
+## Table of Contents
+
+1. [Overview](#overview)
+2. [Drizzle Migration Internals](#drizzle-migration-internals)
+3. [Migration Commands](#migration-commands)
+4. [Development vs Production](#development-vs-production)
+5. [CI/CD Pipeline](#cicd-pipeline)
+6. [Advisory Locks](#advisory-locks)
+7. [Zero-Downtime Migrations](#zero-downtime-migrations)
+8. [Rollback Procedures](#rollback-procedures)
+9. [Troubleshooting](#troubleshooting)
+
+---
+
+## Overview
+
+All backends in the ManaCore monorepo use **Drizzle ORM** for database schema management. We use two different approaches depending on the environment:
+
+| Environment | Command | Purpose |
+|-------------|---------|---------|
+| **Development** | `drizzle-kit push` | Fast iteration, direct schema sync |
+| **Production** | `drizzle-kit generate` + `migrate` | Tracked migrations with history |
+
+### Key Principles
+
+1. **Migrations run BEFORE code deployment** - Ensures database is ready for new code
+2. **Advisory locks prevent concurrent migrations** - Safe for multi-replica deployments
+3. **Expand-contract pattern for breaking changes** - Zero-downtime schema changes
+4. **Data persistence** - Migrations never delete user data unless explicitly requested
+
+### Quick Decision Guide
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ Which command should I use? │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ Local development? │
+│ └── YES → pnpm db:push (fast, no tracking) │
+│ │
+│ Staging/Production? │
+│ └── YES → pnpm db:generate + pnpm db:migrate (tracked) │
+│ │
+│ Need to inspect data? │
+│ └── YES → pnpm db:studio (opens Drizzle Studio) │
+│ │
+│ Schema changed by someone else? │
+│ └── YES → git pull + pnpm db:push (local) │
+│ git pull + pnpm db:migrate (staging/prod) │
+│ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+---
+
+## Drizzle Migration Internals
+
+Understanding how Drizzle manages migrations is essential for debugging issues.
+
+### The Two Modes
+
+#### 1. Push Mode (`drizzle-kit push`)
+
+**How it works:**
+1. Drizzle introspects your TypeScript schema files
+2. Drizzle introspects the current database schema
+3. Drizzle computes the diff between them
+4. Drizzle generates and **immediately executes** the SQL to sync them
+
+**Characteristics:**
+- No migration files created
+- No history tracking
+- Direct database modification
+- Interactive confirmation (use `--force` to skip)
+
+**When to use:** Local development, experimentation, prototyping
+
+#### 2. Generate + Migrate Mode (`drizzle-kit generate` + `migrate`)
+
+**How it works:**
+
+**Step 1: Generate** (`drizzle-kit generate`)
+1. Drizzle introspects your TypeScript schema files
+2. Drizzle reads the last snapshot from `migrations/meta/`
+3. Drizzle computes the diff
+4. Drizzle creates migration files (SQL + snapshot)
+
+**Step 2: Migrate** (`pnpm db:migrate`)
+1. Script reads `migrations/meta/_journal.json`
+2. Script queries `__drizzle_migrations` table in database
+3. Script determines which migrations haven't been applied
+4. Script executes pending migrations in order
+5. Script records applied migrations in `__drizzle_migrations`
+
+**Characteristics:**
+- Creates versioned SQL files
+- Full history tracking
+- Repeatable deployments
+- Can be reviewed before applying
+
+**When to use:** Staging, production, CI/CD pipelines
+
+### Migration File Structure
+
+```
+src/db/migrations/
+├── 0000_initial_schema/
+│ ├── migration.sql # The actual SQL to execute
+│ └── snapshot.json # Schema snapshot AFTER this migration
+├── 0001_add_user_preferences/
+│ ├── migration.sql
+│ └── snapshot.json
+├── 0002_add_credits_table/
+│ ├── migration.sql
+│ └── snapshot.json
+└── meta/
+ └── _journal.json # Migration registry (order + metadata)
+```
+
+### The Journal File (`_journal.json`)
+
+This file tracks all generated migrations:
+
+```json
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1733066521000,
+ "tag": "0000_initial_schema",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1733152921000,
+ "tag": "0001_add_user_preferences",
+ "breakpoints": true
+ }
+ ]
+}
+```
+
+**Key fields:**
+- `idx`: Sequential index (order matters!)
+- `tag`: Folder name containing the migration
+- `when`: Unix timestamp when generated
+- `breakpoints`: Whether to use statement breakpoints
+
+### The Database Tracking Table (`__drizzle_migrations`)
+
+Drizzle creates this table automatically to track applied migrations:
+
+```sql
+-- Schema: drizzle
+-- Table: __drizzle_migrations
+CREATE TABLE drizzle.__drizzle_migrations (
+ id SERIAL PRIMARY KEY,
+ hash TEXT NOT NULL,
+ created_at BIGINT NOT NULL
+);
+```
+
+**Query applied migrations:**
+```sql
+SELECT * FROM drizzle.__drizzle_migrations ORDER BY created_at;
+```
+
+### How Migration Tracking Works
+
+```
+┌─────────────────┐ ┌─────────────────┐
+│ _journal.json │ │ __drizzle_ │
+│ (filesystem) │ │ migrations (db) │
+└────────┬────────┘ └────────┬────────┘
+ │ │
+ ▼ ▼
+ [0000, 0001, 0002] [hash_0000, hash_0001]
+ │ │
+ └───────────┬───────────┘
+ │
+ ▼
+ Pending: [0002]
+ │
+ ▼
+ Execute 0002/migration.sql
+ │
+ ▼
+ Insert into __drizzle_migrations
+```
+
+### Snapshot Files
+
+Each migration includes a `snapshot.json` that captures the **complete schema state** after that migration. This allows Drizzle to:
+
+1. Compute diffs for the next migration
+2. Detect schema drift
+3. Generate accurate SQL
+
+**Important:** Never modify snapshots manually!
+
+---
+
+## Migration Commands
+
+### All Backends
+
+```bash
+# Development - push schema directly (fast, no history)
+pnpm db:push
+
+# Generate migration files from schema changes
+pnpm db:generate
+
+# Run migrations with advisory locks (production-safe)
+pnpm db:migrate
+
+# Open Drizzle Studio for database inspection
+pnpm db:studio
+```
+
+### Root-Level Commands
+
+```bash
+# Setup all databases (creates DBs + pushes schemas)
+pnpm setup:db
+
+# Setup specific service
+pnpm setup:db:auth
+pnpm setup:db:chat
+```
+
+### Per-Service Commands
+
+```bash
+# mana-core-auth
+pnpm --filter mana-core-auth db:push
+pnpm --filter mana-core-auth db:generate
+pnpm --filter mana-core-auth db:migrate
+
+# chat-backend
+pnpm --filter @chat/backend db:push
+pnpm --filter @chat/backend db:migrate
+```
+
+---
+
+## Development vs Production
+
+### Development Workflow
+
+For local development, use `db:push` for fast iteration:
+
+```bash
+# 1. Make schema changes in src/db/schema/*.ts
+# 2. Push changes to local database
+pnpm db:push
+
+# Or use the full dev command which handles this automatically
+pnpm dev:chat:full
+```
+
+**Why `push` for development?**
+- Instant feedback on schema changes
+- No migration file clutter during experimentation
+- Automatically handled by `dev:*:full` commands
+
+### Production Workflow
+
+For staging/production, use migration files for trackability:
+
+```bash
+# 1. Make schema changes in src/db/schema/*.ts
+
+# 2. Generate migration file
+pnpm db:generate --name add_user_preferences
+
+# 3. Review generated SQL
+cat src/db/migrations/*/migration.sql
+
+# 4. Commit migration files
+git add src/db/migrations/
+git commit -m "feat: add user preferences table"
+
+# 5. CI/CD runs migrations automatically on deploy
+```
+
+**Why migrations for production?**
+- Audit trail of all schema changes
+- Repeatable deployments
+- Rollback capability (with manual down migrations)
+
+---
+
+## CI/CD Pipeline
+
+### Deployment Flow
+
+```
+┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
+│ Build │───>│ Create DB │───>│ Migrate │───>│ Deploy │
+│ Images │ │ (if new) │ │ Database │ │ Code │
+└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
+```
+
+### Migration Step Features
+
+1. **Retry logic** - 3 attempts with exponential backoff (10s, 20s, 30s)
+2. **Timeout protection** - 5-minute timeout per migration
+3. **Advisory locks** - Prevents concurrent migrations
+4. **Graceful fallback** - Falls back to `db:push` if `db:migrate` unavailable
+
+### Staging Deployment
+
+Migrations run automatically after database creation:
+
+```yaml
+# .github/workflows/cd-staging.yml
+- name: Run database migrations
+ run: |
+ docker compose exec -T mana-core-auth pnpm run db:migrate
+```
+
+### Production Deployment
+
+Migrations run BEFORE deploying new code:
+
+```yaml
+# .github/workflows/cd-production.yml
+- name: Run database migrations
+ run: |
+ docker compose run --rm mana-core-auth pnpm run db:migrate
+
+- name: Deploy with zero-downtime
+ run: |
+ docker compose up -d
+```
+
+---
+
+## Advisory Locks
+
+Advisory locks prevent multiple instances from running migrations simultaneously.
+
+### How It Works
+
+```typescript
+// services/mana-core-auth/src/db/migrate.ts
+
+const MIGRATION_LOCK_ID = 987654321;
+
+// Acquire lock before migration
+await db.execute(sql`SELECT pg_try_advisory_lock(${LOCK_ID})`);
+
+// Run migrations...
+
+// Release lock after migration
+await db.execute(sql`SELECT pg_advisory_unlock(${LOCK_ID})`);
+```
+
+### Lock Behavior
+
+| Scenario | Behavior |
+|----------|----------|
+| Lock acquired | Migration runs immediately |
+| Lock held by another process | Waits up to 5 minutes, then fails |
+| Lock stuck | Manual release required (see Troubleshooting) |
+
+### Lock IDs by Service
+
+| Service | Lock ID |
+|---------|---------|
+| mana-core-auth | `987654321` |
+| chat-backend | (to be assigned) |
+| todo-backend | (to be assigned) |
+
+### Migration Script Architecture
+
+The production migration script (`src/db/migrate.ts`) is designed for safe, concurrent-safe deployments:
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ migrate.ts Execution Flow │
+├─────────────────────────────────────────────────────────────────┤
+│ │
+│ 1. Load environment variables (.env) │
+│ └── DATABASE_URL, MIGRATION_TIMEOUT │
+│ │
+│ 2. Create single-connection pool │
+│ └── max: 1 (dedicated migration connection) │
+│ │
+│ 3. Test database connectivity (with retry) │
+│ └── SELECT 1 (max 3 attempts, exponential backoff) │
+│ │
+│ 4. Acquire advisory lock │
+│ ├── pg_try_advisory_lock() - non-blocking attempt │
+│ └── If busy: poll every 5s until timeout (default: 5 min) │
+│ │
+│ 5. Check for migration files │
+│ └── If meta/_journal.json missing: exit gracefully │
+│ │
+│ 6. Run Drizzle migrations │
+│ └── migrate(db, { migrationsFolder }) │
+│ │
+│ 7. Cleanup (always runs, even on error) │
+│ ├── Release advisory lock │
+│ └── Close database connection │
+│ │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+**Key Components:**
+
+| Component | Purpose | Configuration |
+|-----------|---------|---------------|
+| `withRetry()` | Retry transient errors (network, connection) | 3 attempts, exponential backoff |
+| `acquireLock()` | Non-blocking lock attempt | `pg_try_advisory_lock()` |
+| `waitForLock()` | Polling wait for lock | 5s intervals, configurable timeout |
+| `releaseLock()` | Release lock in finally block | Always runs |
+
+**Error Handling:**
+
+```typescript
+// Transient errors (will retry):
+- ECONNREFUSED, ETIMEDOUT, ENOTFOUND
+- Connection errors
+- PostgreSQL 57P03 (cannot connect now)
+
+// Non-transient errors (immediate failure):
+- Missing DATABASE_URL
+- SQL syntax errors
+- Schema conflicts
+- Lock timeout
+```
+
+**Exit Codes:**
+
+| Code | Meaning |
+|------|---------|
+| 0 | Success - all migrations applied |
+| 1 | Failure - check logs for details |
+
+---
+
+## Zero-Downtime Migrations
+
+For breaking schema changes, use the **expand-contract pattern**:
+
+### Phase 1: Expand
+
+Add new schema elements alongside existing ones:
+
+```sql
+-- Migration: 001_add_full_name.sql
+ALTER TABLE users ADD COLUMN full_name TEXT;
+```
+
+### Phase 2: Migrate
+
+Update application to write to both, backfill data:
+
+```typescript
+// Application code - dual write
+await db.update(users).set({
+ name: newName, // Old column
+ fullName: newName, // New column
+});
+
+// Backfill script
+UPDATE users SET full_name = name WHERE full_name IS NULL;
+```
+
+### Phase 3: Contract
+
+After 1-2 weeks, remove old column:
+
+```sql
+-- Migration: 002_drop_name_column.sql
+ALTER TABLE users DROP COLUMN name;
+```
+
+### Common Patterns
+
+| Change Type | Approach |
+|-------------|----------|
+| Add column | Direct `ALTER TABLE ADD COLUMN` |
+| Drop column | Remove from code first, wait 2 weeks, then drop |
+| Rename column | Add new → dual-write → backfill → drop old |
+| Change type | Add new column → backfill with cast → swap |
+| Add NOT NULL | Add nullable → backfill → add constraint |
+
+### Index Creation
+
+Always use `CONCURRENTLY` to avoid table locks:
+
+```sql
+-- Good
+CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
+
+-- Bad (locks table)
+CREATE INDEX idx_users_email ON users(email);
+```
+
+---
+
+## Rollback Procedures
+
+### Automatic Rollback (Not Supported)
+
+Drizzle ORM does not support automatic rollbacks. Plan your migrations carefully.
+
+### Manual Rollback
+
+1. **Write down migration scripts** alongside up migrations:
+
+```
+src/db/migrations/
+├── 001_add_referrals.up.sql
+├── 001_add_referrals.down.sql # Manual rollback script
+```
+
+2. **Execute rollback manually**:
+
+```bash
+# Connect to database
+docker compose exec -T postgres psql -U postgres -d manacore_auth
+
+# Run down migration
+\i /path/to/001_add_referrals.down.sql
+```
+
+### Rollback Checklist
+
+- [ ] Identify affected migration
+- [ ] Verify rollback script exists and is tested
+- [ ] Create database backup before rollback
+- [ ] Execute rollback in staging first
+- [ ] Monitor for issues after rollback
+- [ ] Update application code if needed
+
+---
+
+## Troubleshooting
+
+### Migration Lock Stuck
+
+If a migration lock is stuck (process crashed without releasing):
+
+```sql
+-- Check for stuck locks
+SELECT * FROM pg_locks WHERE locktype = 'advisory';
+
+-- Release specific lock (replace LOCK_ID)
+SELECT pg_advisory_unlock(987654321);
+
+-- Release all advisory locks for current session
+SELECT pg_advisory_unlock_all();
+```
+
+### Migration Timeout
+
+If migrations time out:
+
+1. Check for long-running queries: `SELECT * FROM pg_stat_activity;`
+2. Increase timeout: `MIGRATION_TIMEOUT=600 pnpm db:migrate`
+3. Break large migrations into smaller steps
+
+### Schema Drift
+
+If staging/production schema differs from expected:
+
+```bash
+# Generate migration from current schema
+pnpm db:generate --name sync_schema
+
+# Review and apply
+pnpm db:migrate
+```
+
+### Connection Issues
+
+```bash
+# Test database connectivity
+docker compose exec -T postgres pg_isready -U postgres
+
+# Check environment variables
+echo $DATABASE_URL
+
+# Manual connection test
+docker compose exec -T postgres psql -U postgres -d manacore_auth -c "SELECT 1"
+```
+
+### Migration Fails in CI/CD
+
+1. Check GitHub Actions logs for specific error
+2. Verify DATABASE_URL is correctly set in secrets
+3. Ensure database exists before migration runs
+4. Check if another migration is running (advisory lock)
+
+---
+
+## Best Practices
+
+### DO
+
+- Run migrations before deploying new code
+- Test migrations in staging before production
+- Use `CONCURRENTLY` for index creation
+- Keep migrations small and focused
+- Commit migration files to version control
+- Wait 1-2 weeks before dropping columns
+
+### DON'T
+
+- Run `db:push` in production
+- Delete migration files after they've been applied
+- Modify migration files after they've been applied
+- Add NOT NULL without default or backfill
+- Create indexes without `CONCURRENTLY`
+- Drop columns immediately after removing from code
+
+---
+
+## Migration File Structure
+
+```
+services/mana-core-auth/
+├── src/db/
+│ ├── schema/
+│ │ ├── index.ts # Export all schemas
+│ │ ├── auth.schema.ts # User, session tables
+│ │ └── credits.schema.ts # Credit system tables
+│ ├── migrations/
+│ │ ├── 0001_initial/
+│ │ │ ├── snapshot.json
+│ │ │ └── migration.sql
+│ │ └── meta/
+│ │ └── _journal.json # Migration history
+│ ├── connection.ts # Database connection
+│ └── migrate.ts # Migration script with locks
+└── drizzle.config.ts # Drizzle configuration
+```
+
+---
+
+## Environment Variables
+
+| Variable | Description | Default |
+|----------|-------------|---------|
+| `DATABASE_URL` | PostgreSQL connection string | Required |
+| `MIGRATION_TIMEOUT` | Max seconds for migration | `300` |
+
+---
+
+## References
+
+- [Drizzle ORM Migrations](https://orm.drizzle.team/docs/migrations)
+- [PostgreSQL Advisory Locks](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS)
+- [Expand-Contract Pattern](https://martinfowler.com/bliki/ParallelChange.html)
+- [Zero-Downtime PostgreSQL Migrations](https://postgres.ai/blog/20210923-zero-downtime-postgres-schema-migrations-lock-timeout-and-retries)
diff --git a/services/mana-core-auth/CLAUDE.md b/services/mana-core-auth/CLAUDE.md
index fff9c7d9a..d7d321613 100644
--- a/services/mana-core-auth/CLAUDE.md
+++ b/services/mana-core-auth/CLAUDE.md
@@ -91,7 +91,9 @@ services/mana-core-auth/
│ ├── credits/ # Credit system
│ ├── db/
│ │ ├── schema/ # Drizzle schemas
-│ │ └── connection.ts # DB connection
+│ │ ├── migrations/ # Generated migration files
+│ │ ├── connection.ts # DB connection
+│ │ └── migrate.ts # Migration script with advisory locks
│ └── config/
│ └── configuration.ts # App config
├── docs/
@@ -99,6 +101,16 @@ services/mana-core-auth/
└── test/
```
+## Database Migrations
+
+For comprehensive migration documentation, see **[docs/DATABASE_MIGRATIONS.md](/docs/DATABASE_MIGRATIONS.md)**.
+
+Key points:
+- Use `db:push` for development (fast iteration)
+- Use `db:generate` + `db:migrate` for production (tracked migrations)
+- Migrations use advisory locks to prevent concurrent execution
+- CI/CD runs migrations automatically before code deployment
+
## Key Files
| File | Purpose |
diff --git a/services/mana-core-auth/package.json b/services/mana-core-auth/package.json
index ae517f1d9..1eb6fb0fc 100644
--- a/services/mana-core-auth/package.json
+++ b/services/mana-core-auth/package.json
@@ -16,6 +16,8 @@
"test:cov": "jest --coverage",
"test:e2e": "jest --config ./test/jest-e2e.json",
"db:push": "drizzle-kit push",
+ "db:generate": "drizzle-kit generate",
+ "db:migrate": "tsx src/db/migrate.ts",
"db:studio": "drizzle-kit studio"
},
"dependencies": {
diff --git a/services/mana-core-auth/src/db/migrate.ts b/services/mana-core-auth/src/db/migrate.ts
new file mode 100644
index 000000000..3bff51152
--- /dev/null
+++ b/services/mana-core-auth/src/db/migrate.ts
@@ -0,0 +1,222 @@
+/**
+ * Database Migration Script with Advisory Locks
+ *
+ * This script safely runs database migrations with the following features:
+ * - Advisory locks to prevent concurrent migrations
+ * - Retry logic for transient network failures
+ * - Timeout protection
+ * - Proper cleanup on exit
+ * - Graceful handling when no migrations exist
+ *
+ * Usage:
+ * pnpm db:migrate # Run migrations
+ * MIGRATION_TIMEOUT=600 pnpm db:migrate # With custom timeout (seconds)
+ */
+
+import { drizzle } from 'drizzle-orm/postgres-js';
+import { migrate } from 'drizzle-orm/postgres-js/migrator';
+import { sql } from 'drizzle-orm';
+import postgres from 'postgres';
+import * as dotenv from 'dotenv';
+import * as fs from 'fs';
+import * as path from 'path';
+
+// Load environment variables
+dotenv.config();
+
+// Configuration
+const MIGRATION_LOCK_ID = 987654321; // Unique lock ID for mana-core-auth migrations
+const MAX_LOCK_WAIT_MS = parseInt(process.env.MIGRATION_TIMEOUT || '300', 10) * 1000; // Default 5 minutes
+const MAX_RETRIES = 3;
+const RETRY_DELAY_MS = 2000;
+
+/**
+ * Retry wrapper for transient errors
+ */
+async function withRetry(
+ operation: () => Promise,
+ operationName: string,
+ maxRetries = MAX_RETRIES
+): Promise {
+ let lastError: Error | undefined;
+
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
+ try {
+ return await operation();
+ } catch (error) {
+ lastError = error as Error;
+
+ // Check if error is transient (network-related)
+ const isTransient =
+ lastError.message?.includes('ECONNREFUSED') ||
+ lastError.message?.includes('ETIMEDOUT') ||
+ lastError.message?.includes('ENOTFOUND') ||
+ lastError.message?.includes('connection') ||
+ (lastError as any).code === '57P03'; // PostgreSQL: cannot connect now
+
+ if (!isTransient || attempt === maxRetries) {
+ throw error;
+ }
+
+ const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1); // Exponential backoff
+ console.log(
+ `\u26a0\ufe0f [${operationName}] Transient error, retrying in ${delay}ms... (attempt ${attempt}/${maxRetries})`
+ );
+ console.log(` Error: ${lastError.message}`);
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ }
+ }
+
+ throw lastError!;
+}
+
+/**
+ * Acquire PostgreSQL advisory lock
+ */
+async function acquireLock(db: ReturnType): Promise {
+ const result = await db.execute(
+ sql`SELECT pg_try_advisory_lock(${MIGRATION_LOCK_ID}) as acquired`
+ );
+ return (result as any)[0]?.acquired === true;
+}
+
+/**
+ * Release PostgreSQL advisory lock
+ */
+async function releaseLock(db: ReturnType): Promise {
+ await db.execute(sql`SELECT pg_advisory_unlock(${MIGRATION_LOCK_ID})`);
+}
+
+/**
+ * Wait for migration lock with timeout
+ */
+async function waitForLock(db: ReturnType): Promise {
+ const startTime = Date.now();
+
+ while (Date.now() - startTime < MAX_LOCK_WAIT_MS) {
+ const acquired = await acquireLock(db);
+ if (acquired) {
+ return true;
+ }
+
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
+ console.log(`\u23f3 Waiting for migration lock... (${elapsed}s / ${MAX_LOCK_WAIT_MS / 1000}s)`);
+ await new Promise((resolve) => setTimeout(resolve, 5000));
+ }
+
+ return false;
+}
+
+/**
+ * Main migration function
+ */
+async function runMigrations(): Promise {
+ const databaseUrl = process.env.DATABASE_URL;
+
+ if (!databaseUrl) {
+ throw new Error('DATABASE_URL environment variable is not set');
+ }
+
+ console.log('\n\ud83d\udd04 Starting database migration process...');
+ console.log(` Lock ID: ${MIGRATION_LOCK_ID}`);
+ console.log(` Timeout: ${MAX_LOCK_WAIT_MS / 1000}s`);
+ console.log('');
+
+ // Create connection with single connection for migrations
+ const connection = postgres(databaseUrl, {
+ max: 1,
+ idle_timeout: 20,
+ connect_timeout: 30,
+ });
+
+ const db = drizzle(connection);
+ let lockAcquired = false;
+
+ try {
+ // Test database connection
+ console.log('\ud83d\udd0c Testing database connection...');
+ await withRetry(async () => {
+ await db.execute(sql`SELECT 1`);
+ }, 'Database connection');
+ console.log('\u2705 Database connection successful\n');
+
+ // Attempt to acquire advisory lock
+ console.log('\ud83d\udd12 Attempting to acquire migration lock...');
+
+ lockAcquired = await withRetry(() => acquireLock(db), 'Acquire lock');
+
+ if (!lockAcquired) {
+ console.log('\u23f3 Another instance is running migrations. Waiting for lock...');
+
+ lockAcquired = await waitForLock(db);
+
+ if (!lockAcquired) {
+ throw new Error(
+ `Migration lock timeout after ${MAX_LOCK_WAIT_MS / 1000}s - another migration may be stuck`
+ );
+ }
+ }
+
+ console.log('\u2705 Migration lock acquired\n');
+
+ // Check if migration files exist
+ const migrationsFolder = './src/db/migrations';
+ const journalPath = path.join(migrationsFolder, 'meta', '_journal.json');
+
+ if (!fs.existsSync(journalPath)) {
+ console.log('\u26a0\ufe0f No migration files found (meta/_journal.json missing)');
+ console.log(' This is normal if you have not generated any migrations yet.');
+ console.log(' To generate migrations, run: pnpm db:generate');
+ console.log(' For development, you can use: pnpm db:push');
+ console.log('\n\u2705 No migrations to run\n');
+ return;
+ }
+
+ // Run migrations
+ console.log('\ud83d\udce6 Running database migrations...');
+
+ await withRetry(
+ async () => {
+ await migrate(db, {
+ migrationsFolder,
+ });
+ },
+ 'Run migrations',
+ 1 // Only 1 attempt for actual migrations (they should be idempotent)
+ );
+
+ console.log('\u2705 Migrations completed successfully\n');
+ } catch (error) {
+ console.error('\n\u274c Migration failed:', error);
+ throw error;
+ } finally {
+ // Always attempt to release lock
+ if (lockAcquired) {
+ try {
+ await releaseLock(db);
+ console.log('\ud83d\udd13 Migration lock released');
+ } catch (unlockError) {
+ console.error('\u26a0\ufe0f Failed to release lock:', unlockError);
+ }
+ }
+
+ // Close connection
+ try {
+ await connection.end();
+ console.log('\ud83d\udd0c Database connection closed\n');
+ } catch (closeError) {
+ console.error('\u26a0\ufe0f Failed to close connection:', closeError);
+ }
+ }
+}
+
+// Run migrations
+runMigrations()
+ .then(() => {
+ console.log('\ud83c\udf89 Migration process completed successfully');
+ process.exit(0);
+ })
+ .catch((error) => {
+ console.error('\n\ud83d\udca5 Migration process failed:', error.message);
+ process.exit(1);
+ });
diff --git a/services/mana-core-auth/src/db/migrations/0001_zippy_ma_gnuci.sql b/services/mana-core-auth/src/db/migrations/0001_zippy_ma_gnuci.sql
deleted file mode 100644
index 2ad2cfbfc..000000000
--- a/services/mana-core-auth/src/db/migrations/0001_zippy_ma_gnuci.sql
+++ /dev/null
@@ -1,39 +0,0 @@
-CREATE SCHEMA "feedback";
---> statement-breakpoint
-CREATE TYPE "public"."feedback_category" AS ENUM('bug', 'feature', 'improvement', 'question', 'other');--> statement-breakpoint
-CREATE TYPE "public"."feedback_status" AS ENUM('submitted', 'under_review', 'planned', 'in_progress', 'completed', 'declined');--> statement-breakpoint
-CREATE TABLE "feedback"."feedback_votes" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "feedback_id" uuid NOT NULL,
- "user_id" uuid NOT NULL,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL
-);
---> statement-breakpoint
-CREATE TABLE "feedback"."user_feedback" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "user_id" uuid NOT NULL,
- "app_id" text NOT NULL,
- "title" text,
- "feedback_text" text NOT NULL,
- "category" "feedback_category" DEFAULT 'feature' NOT NULL,
- "status" "feedback_status" DEFAULT 'submitted' NOT NULL,
- "is_public" boolean DEFAULT false NOT NULL,
- "admin_response" text,
- "vote_count" integer DEFAULT 0 NOT NULL,
- "device_info" jsonb,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
- "published_at" timestamp with time zone,
- "completed_at" timestamp with time zone
-);
---> statement-breakpoint
-ALTER TABLE "feedback"."feedback_votes" ADD CONSTRAINT "feedback_votes_feedback_id_user_feedback_id_fk" FOREIGN KEY ("feedback_id") REFERENCES "feedback"."user_feedback"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "feedback"."feedback_votes" ADD CONSTRAINT "feedback_votes_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "feedback"."user_feedback" ADD CONSTRAINT "user_feedback_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-CREATE UNIQUE INDEX "feedback_vote_unique" ON "feedback"."feedback_votes" USING btree ("feedback_id","user_id");--> statement-breakpoint
-CREATE INDEX "feedback_votes_feedback_idx" ON "feedback"."feedback_votes" USING btree ("feedback_id");--> statement-breakpoint
-CREATE INDEX "feedback_user_idx" ON "feedback"."user_feedback" USING btree ("user_id");--> statement-breakpoint
-CREATE INDEX "feedback_app_idx" ON "feedback"."user_feedback" USING btree ("app_id");--> statement-breakpoint
-CREATE INDEX "feedback_public_idx" ON "feedback"."user_feedback" USING btree ("is_public");--> statement-breakpoint
-CREATE INDEX "feedback_status_idx" ON "feedback"."user_feedback" USING btree ("status");--> statement-breakpoint
-CREATE INDEX "feedback_created_at_idx" ON "feedback"."user_feedback" USING btree ("created_at");
\ No newline at end of file
diff --git a/services/mana-core-auth/src/db/migrations/meta/0001_snapshot.json b/services/mana-core-auth/src/db/migrations/meta/0001_snapshot.json
deleted file mode 100644
index c3e223168..000000000
--- a/services/mana-core-auth/src/db/migrations/meta/0001_snapshot.json
+++ /dev/null
@@ -1,1501 +0,0 @@
-{
- "id": "ecb1358f-1cde-49ae-973b-4a2d9d7d5c2b",
- "prevId": "83697ac3-d241-4743-96a1-880ad990aa0b",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "auth.accounts": {
- "name": "accounts",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "provider": {
- "name": "provider",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "provider_account_id": {
- "name": "provider_account_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "access_token": {
- "name": "access_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "refresh_token": {
- "name": "refresh_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "expires_at": {
- "name": "expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "token_type": {
- "name": "token_type",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "scope": {
- "name": "scope",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "id_token": {
- "name": "id_token",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "accounts_user_id_users_id_fk": {
- "name": "accounts_user_id_users_id_fk",
- "tableFrom": "accounts",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.passwords": {
- "name": "passwords",
- "schema": "auth",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "hashed_password": {
- "name": "hashed_password",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "passwords_user_id_users_id_fk": {
- "name": "passwords_user_id_users_id_fk",
- "tableFrom": "passwords",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.security_events": {
- "name": "security_events",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "event_type": {
- "name": "event_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "ip_address": {
- "name": "ip_address",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "user_agent": {
- "name": "user_agent",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "security_events_user_id_users_id_fk": {
- "name": "security_events_user_id_users_id_fk",
- "tableFrom": "security_events",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.sessions": {
- "name": "sessions",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "token": {
- "name": "token",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "refresh_token": {
- "name": "refresh_token",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "refresh_token_expires_at": {
- "name": "refresh_token_expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "ip_address": {
- "name": "ip_address",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "user_agent": {
- "name": "user_agent",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "device_id": {
- "name": "device_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "device_name": {
- "name": "device_name",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "last_activity_at": {
- "name": "last_activity_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "expires_at": {
- "name": "expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "revoked_at": {
- "name": "revoked_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "sessions_user_id_users_id_fk": {
- "name": "sessions_user_id_users_id_fk",
- "tableFrom": "sessions",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "sessions_token_unique": {
- "name": "sessions_token_unique",
- "nullsNotDistinct": false,
- "columns": ["token"]
- },
- "sessions_refresh_token_unique": {
- "name": "sessions_refresh_token_unique",
- "nullsNotDistinct": false,
- "columns": ["refresh_token"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.two_factor_auth": {
- "name": "two_factor_auth",
- "schema": "auth",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "secret": {
- "name": "secret",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "enabled": {
- "name": "enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "backup_codes": {
- "name": "backup_codes",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "enabled_at": {
- "name": "enabled_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "two_factor_auth_user_id_users_id_fk": {
- "name": "two_factor_auth_user_id_users_id_fk",
- "tableFrom": "two_factor_auth",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.users": {
- "name": "users",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "email_verified": {
- "name": "email_verified",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "avatar_url": {
- "name": "avatar_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "role": {
- "name": "role",
- "type": "user_role",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'user'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "deleted_at": {
- "name": "deleted_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "users_email_unique": {
- "name": "users_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "auth.verification_tokens": {
- "name": "verification_tokens",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "token": {
- "name": "token",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "expires_at": {
- "name": "expires_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "used_at": {
- "name": "used_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {
- "verification_tokens_user_id_users_id_fk": {
- "name": "verification_tokens_user_id_users_id_fk",
- "tableFrom": "verification_tokens",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "verification_tokens_token_unique": {
- "name": "verification_tokens_token_unique",
- "nullsNotDistinct": false,
- "columns": ["token"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.balances": {
- "name": "balances",
- "schema": "credits",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "balance": {
- "name": "balance",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "free_credits_remaining": {
- "name": "free_credits_remaining",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 150
- },
- "daily_free_credits": {
- "name": "daily_free_credits",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 5
- },
- "last_daily_reset_at": {
- "name": "last_daily_reset_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false,
- "default": "now()"
- },
- "total_earned": {
- "name": "total_earned",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "total_spent": {
- "name": "total_spent",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "version": {
- "name": "version",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "balances_user_id_users_id_fk": {
- "name": "balances_user_id_users_id_fk",
- "tableFrom": "balances",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.packages": {
- "name": "packages",
- "schema": "credits",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "credits": {
- "name": "credits",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "price_euro_cents": {
- "name": "price_euro_cents",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "stripe_price_id": {
- "name": "stripe_price_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "active": {
- "name": "active",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "sort_order": {
- "name": "sort_order",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "packages_stripe_price_id_unique": {
- "name": "packages_stripe_price_id_unique",
- "nullsNotDistinct": false,
- "columns": ["stripe_price_id"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.purchases": {
- "name": "purchases",
- "schema": "credits",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "package_id": {
- "name": "package_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "credits": {
- "name": "credits",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "price_euro_cents": {
- "name": "price_euro_cents",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "stripe_payment_intent_id": {
- "name": "stripe_payment_intent_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "stripe_customer_id": {
- "name": "stripe_customer_id",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "status": {
- "name": "status",
- "type": "transaction_status",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'pending'"
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "completed_at": {
- "name": "completed_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {
- "purchases_user_id_idx": {
- "name": "purchases_user_id_idx",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "purchases_stripe_payment_intent_id_idx": {
- "name": "purchases_stripe_payment_intent_id_idx",
- "columns": [
- {
- "expression": "stripe_payment_intent_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "purchases_user_id_users_id_fk": {
- "name": "purchases_user_id_users_id_fk",
- "tableFrom": "purchases",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "purchases_package_id_packages_id_fk": {
- "name": "purchases_package_id_packages_id_fk",
- "tableFrom": "purchases",
- "tableTo": "packages",
- "schemaTo": "credits",
- "columnsFrom": ["package_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "purchases_stripe_payment_intent_id_unique": {
- "name": "purchases_stripe_payment_intent_id_unique",
- "nullsNotDistinct": false,
- "columns": ["stripe_payment_intent_id"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.transactions": {
- "name": "transactions",
- "schema": "credits",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "transaction_type",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true
- },
- "status": {
- "name": "status",
- "type": "transaction_status",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'pending'"
- },
- "amount": {
- "name": "amount",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "balance_before": {
- "name": "balance_before",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "balance_after": {
- "name": "balance_after",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "app_id": {
- "name": "app_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "idempotency_key": {
- "name": "idempotency_key",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "completed_at": {
- "name": "completed_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {
- "transactions_user_id_idx": {
- "name": "transactions_user_id_idx",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "transactions_app_id_idx": {
- "name": "transactions_app_id_idx",
- "columns": [
- {
- "expression": "app_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "transactions_created_at_idx": {
- "name": "transactions_created_at_idx",
- "columns": [
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "transactions_idempotency_key_idx": {
- "name": "transactions_idempotency_key_idx",
- "columns": [
- {
- "expression": "idempotency_key",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "transactions_user_id_users_id_fk": {
- "name": "transactions_user_id_users_id_fk",
- "tableFrom": "transactions",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "transactions_idempotency_key_unique": {
- "name": "transactions_idempotency_key_unique",
- "nullsNotDistinct": false,
- "columns": ["idempotency_key"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "credits.usage_stats": {
- "name": "usage_stats",
- "schema": "credits",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "app_id": {
- "name": "app_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "credits_used": {
- "name": "credits_used",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "date": {
- "name": "date",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true
- },
- "metadata": {
- "name": "metadata",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {
- "usage_stats_user_id_date_idx": {
- "name": "usage_stats_user_id_date_idx",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "date",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "usage_stats_app_id_date_idx": {
- "name": "usage_stats_app_id_date_idx",
- "columns": [
- {
- "expression": "app_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "date",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "usage_stats_user_id_users_id_fk": {
- "name": "usage_stats_user_id_users_id_fk",
- "tableFrom": "usage_stats",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "feedback.feedback_votes": {
- "name": "feedback_votes",
- "schema": "feedback",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "feedback_id": {
- "name": "feedback_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "feedback_vote_unique": {
- "name": "feedback_vote_unique",
- "columns": [
- {
- "expression": "feedback_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": true,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_votes_feedback_idx": {
- "name": "feedback_votes_feedback_idx",
- "columns": [
- {
- "expression": "feedback_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "feedback_votes_feedback_id_user_feedback_id_fk": {
- "name": "feedback_votes_feedback_id_user_feedback_id_fk",
- "tableFrom": "feedback_votes",
- "tableTo": "user_feedback",
- "schemaTo": "feedback",
- "columnsFrom": ["feedback_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "feedback_votes_user_id_users_id_fk": {
- "name": "feedback_votes_user_id_users_id_fk",
- "tableFrom": "feedback_votes",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "feedback.user_feedback": {
- "name": "user_feedback",
- "schema": "feedback",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "app_id": {
- "name": "app_id",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "feedback_text": {
- "name": "feedback_text",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "category": {
- "name": "category",
- "type": "feedback_category",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'feature'"
- },
- "status": {
- "name": "status",
- "type": "feedback_status",
- "typeSchema": "public",
- "primaryKey": false,
- "notNull": true,
- "default": "'submitted'"
- },
- "is_public": {
- "name": "is_public",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "admin_response": {
- "name": "admin_response",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "vote_count": {
- "name": "vote_count",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 0
- },
- "device_info": {
- "name": "device_info",
- "type": "jsonb",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "published_at": {
- "name": "published_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "completed_at": {
- "name": "completed_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {
- "feedback_user_idx": {
- "name": "feedback_user_idx",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_app_idx": {
- "name": "feedback_app_idx",
- "columns": [
- {
- "expression": "app_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_public_idx": {
- "name": "feedback_public_idx",
- "columns": [
- {
- "expression": "is_public",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_status_idx": {
- "name": "feedback_status_idx",
- "columns": [
- {
- "expression": "status",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "feedback_created_at_idx": {
- "name": "feedback_created_at_idx",
- "columns": [
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "user_feedback_user_id_users_id_fk": {
- "name": "user_feedback_user_id_users_id_fk",
- "tableFrom": "user_feedback",
- "tableTo": "users",
- "schemaTo": "auth",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {
- "public.user_role": {
- "name": "user_role",
- "schema": "public",
- "values": ["user", "admin", "service"]
- },
- "public.transaction_status": {
- "name": "transaction_status",
- "schema": "public",
- "values": ["pending", "completed", "failed", "cancelled"]
- },
- "public.transaction_type": {
- "name": "transaction_type",
- "schema": "public",
- "values": ["purchase", "usage", "refund", "bonus", "expiry", "adjustment"]
- },
- "public.feedback_category": {
- "name": "feedback_category",
- "schema": "public",
- "values": ["bug", "feature", "improvement", "question", "other"]
- },
- "public.feedback_status": {
- "name": "feedback_status",
- "schema": "public",
- "values": ["submitted", "under_review", "planned", "in_progress", "completed", "declined"]
- }
- },
- "schemas": {
- "auth": "auth",
- "credits": "credits",
- "feedback": "feedback"
- },
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-}