diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..8b6c482 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,55 @@ +# Development overlay — hot-reload for backend and web. +# Usage: +# docker compose -f docker-compose.yml -f docker-compose.dev.yml up +# +# Both services mount local source directories so edits are reflected immediately +# without rebuilding the image. Requires node_modules to exist locally +# (run `pnpm install` at repo root first). + +version: '3.9' + +services: + # --------------------------------------------------------------------------- + # Backend dev — tsx hot-reload + # --------------------------------------------------------------------------- + backend: + build: + context: . + dockerfile: backend/Dockerfile + target: builder # Stop at the build stage; no production image + command: > + sh -c "cd /app/backend && node --import tsx src/bootstrap.ts" + volumes: + - ./backend/src:/app/backend/src:ro + - ./shared:/app/shared:ro + - ./.env:/app/.env:ro + environment: + NODE_ENV: development + # Override healthcheck for faster feedback in dev + healthcheck: + interval: 10s + start_period: 5s + + # --------------------------------------------------------------------------- + # Web dev — Vite dev server (HMR) + # --------------------------------------------------------------------------- + web: + image: node:20-alpine + working_dir: /app/web + command: > + sh -c "corepack enable && pnpm run dev --host 0.0.0.0 --port 3048" + volumes: + - ./web:/app/web:ro + - ./shared:/app/shared:ro + - ./web/node_modules:/app/web/node_modules + - ./.env:/app/.env:ro + ports: + - '3048:3048' + environment: + NODE_ENV: development + VITE_PRODUCT_ID: ${VITE_PRODUCT_ID:-invttrdg} + VITE_PLATFORM_URL: ${VITE_PLATFORM_URL:-http://localhost:4003/api} + VITE_TRADING_API_URL: ${VITE_TRADING_API_URL:-http://localhost:4018} + VITE_BACKTEST_ENABLED: ${VITE_BACKTEST_ENABLED:-true} + depends_on: + - backend diff --git a/mobile/.env.example b/mobile/.env.example new file mode 100644 index 0000000..3a5e966 --- /dev/null +++ b/mobile/.env.example @@ -0,0 +1,13 @@ +# ============================================================================= +# ByteLyst Trading — Mobile App Environment Configuration +# Copy this file to .env.local and fill in your values. +# EXPO_PUBLIC_ variables are embedded at build time — do not put secrets here. +# ============================================================================= + +# --- Backend API --- +# URL of the deployed trading backend. Must not have a trailing slash. +EXPO_PUBLIC_TRADING_API_URL=http://localhost:4018/api + +# --- Platform Service --- +# URL of the ByteLyst platform-service (auth, kill-switch, telemetry). +EXPO_PUBLIC_PLATFORM_URL=http://localhost:4003/api diff --git a/scripts/dev.sh b/scripts/dev.sh new file mode 100755 index 0000000..6f1ad6f --- /dev/null +++ b/scripts/dev.sh @@ -0,0 +1,113 @@ +#!/bin/sh +# dev.sh — Start Docker backend + local Vite web dev server +# +# Usage: +# ./scripts/dev.sh # uses backend/.env for backend, web/.env.local for Vite +# REBUILD=1 ./scripts/dev.sh # force Docker rebuild before starting +# +# Requires: +# - Docker running +# - pnpm installed +# - backend/.env populated (copy from backend/.env.example) +# - web/.env.local populated (copy from web/.env.example) +# +# Ports: +# Backend → http://localhost:4018 +# Web → http://localhost:5173 (Vite default) + +set -eu + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +# --------------------------------------------------------------------------- +# Colour helpers (skip if not a TTY) +# --------------------------------------------------------------------------- +if [ -t 1 ]; then + BOLD='\033[1m'; CYAN='\033[0;36m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m'; RESET='\033[0m' +else + BOLD=''; CYAN=''; GREEN=''; YELLOW=''; RESET='' +fi + +log() { printf "${CYAN}[dev]${RESET} %s\n" "$*"; } +ok() { printf "${GREEN}[dev]${RESET} %s\n" "$*"; } +warn() { printf "${YELLOW}[dev]${RESET} %s\n" "$*"; } + +# --------------------------------------------------------------------------- +# Pre-flight checks +# --------------------------------------------------------------------------- +if ! docker info >/dev/null 2>&1; then + printf "Error: Docker is not running.\n" >&2 + exit 1 +fi + +if [ ! -f "$ROOT/backend/.env" ]; then + warn "backend/.env not found — copy backend/.env.example and fill in credentials." + warn " cp backend/.env.example backend/.env" + exit 1 +fi + +if [ ! -f "$ROOT/web/.env.local" ]; then + warn "web/.env.local not found — creating from web/.env.example with defaults." + cp "$ROOT/web/.env.example" "$ROOT/web/.env.local" +fi + +# --------------------------------------------------------------------------- +# Cleanup on exit — always stop the backend container +# --------------------------------------------------------------------------- +cleanup() { + log "Shutting down backend container..." + cd "$ROOT" + docker compose stop backend >/dev/null 2>&1 || true +} +trap cleanup INT TERM EXIT + +# --------------------------------------------------------------------------- +# Start backend in Docker (backend service only, not web) +# --------------------------------------------------------------------------- +cd "$ROOT" + +BUILD_FLAG="" +if [ "${REBUILD:-}" = "1" ]; then + BUILD_FLAG="--build" + log "Rebuilding backend Docker image..." +fi + +log "Starting backend container..." +# Run detached; logs are tailed below +docker compose up backend --detach $BUILD_FLAG + +# --------------------------------------------------------------------------- +# Wait for backend health +# --------------------------------------------------------------------------- +log "Waiting for backend to become healthy..." +ATTEMPTS=0 +MAX_ATTEMPTS=30 +until docker compose exec -T backend wget -qO- http://localhost:4018/health/live >/dev/null 2>&1; do + ATTEMPTS=$((ATTEMPTS + 1)) + if [ "$ATTEMPTS" -ge "$MAX_ATTEMPTS" ]; then + printf "Error: backend did not become healthy after %s attempts.\n" "$MAX_ATTEMPTS" >&2 + docker compose logs --tail=40 backend >&2 + exit 1 + fi + sleep 2 +done +ok "Backend healthy at http://localhost:4018" + +# --------------------------------------------------------------------------- +# Tail backend logs in background +# --------------------------------------------------------------------------- +docker compose logs --follow --tail=20 backend & +LOGS_PID=$! + +# --------------------------------------------------------------------------- +# Start Vite web dev server (foreground — Ctrl+C stops everything via trap) +# --------------------------------------------------------------------------- +log "Starting Vite web dev server..." +printf "\n${BOLD} Web → http://localhost:5173${RESET}\n" +printf "${BOLD} API → http://localhost:4018${RESET}\n\n" + +cd "$ROOT/web" +pnpm dev + +# Kill log tail if Vite exits cleanly +kill "$LOGS_PID" 2>/dev/null || true diff --git a/vendor/bytelyst/api-client/package.json b/vendor/bytelyst/api-client/package.json new file mode 100644 index 0000000..24e445e --- /dev/null +++ b/vendor/bytelyst/api-client/package.json @@ -0,0 +1,23 @@ +{ + "name": "@bytelyst/api-client", + "version": "0.1.0", + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} diff --git a/vendor/bytelyst/auth/package.json b/vendor/bytelyst/auth/package.json new file mode 100644 index 0000000..8fc1a5b --- /dev/null +++ b/vendor/bytelyst/auth/package.json @@ -0,0 +1,30 @@ +{ + "name": "@bytelyst/auth", + "version": "0.1.0", + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "dependencies": { + "@bytelyst/errors": "file:../errors" + }, + "peerDependencies": { + "jose": ">=5.0.0", + "bcryptjs": ">=2.4.0" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} \ No newline at end of file diff --git a/vendor/bytelyst/config/package.json b/vendor/bytelyst/config/package.json new file mode 100644 index 0000000..6aadbf9 --- /dev/null +++ b/vendor/bytelyst/config/package.json @@ -0,0 +1,48 @@ +{ + "name": "@bytelyst/config", + "version": "0.1.0", + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./keyvault": { + "import": "./dist/keyvault.js", + "types": "./dist/keyvault.d.ts" + }, + "./product-identity": { + "import": "./dist/product-identity.js", + "types": "./dist/product-identity.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "peerDependencies": { + "@azure/identity": ">=4.0.0", + "@azure/keyvault-secrets": ">=4.8.0", + "zod": ">=3.20.0" + }, + "peerDependenciesMeta": { + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + } + }, + "devDependencies": { + "@azure/identity": "^4.13.0", + "@azure/keyvault-secrets": "^4.10.0" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} diff --git a/vendor/bytelyst/cosmos/package.json b/vendor/bytelyst/cosmos/package.json new file mode 100644 index 0000000..3bd24c9 --- /dev/null +++ b/vendor/bytelyst/cosmos/package.json @@ -0,0 +1,26 @@ +{ + "name": "@bytelyst/cosmos", + "version": "0.1.0", + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "peerDependencies": { + "@azure/cosmos": ">=4.0.0" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} diff --git a/vendor/bytelyst/errors/package.json b/vendor/bytelyst/errors/package.json new file mode 100644 index 0000000..cdd302b --- /dev/null +++ b/vendor/bytelyst/errors/package.json @@ -0,0 +1,23 @@ +{ + "name": "@bytelyst/errors", + "version": "0.1.0", + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} diff --git a/vendor/bytelyst/kill-switch-client/package.json b/vendor/bytelyst/kill-switch-client/package.json new file mode 100644 index 0000000..b5426b9 --- /dev/null +++ b/vendor/bytelyst/kill-switch-client/package.json @@ -0,0 +1,24 @@ +{ + "name": "@bytelyst/kill-switch-client", + "version": "0.1.0", + "type": "module", + "description": "Browser/React Native-safe kill switch client for platform-service", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} diff --git a/vendor/bytelyst/llm/package.json b/vendor/bytelyst/llm/package.json new file mode 100644 index 0000000..cbe5d2d --- /dev/null +++ b/vendor/bytelyst/llm/package.json @@ -0,0 +1,30 @@ +{ + "name": "@bytelyst/llm", + "version": "0.1.0", + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./testing": { + "import": "./dist/testing.js", + "types": "./dist/testing.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "devDependencies": { + "vitest": "^3.0.0" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} diff --git a/vendor/bytelyst/react-auth/package.json b/vendor/bytelyst/react-auth/package.json new file mode 100644 index 0000000..b73d202 --- /dev/null +++ b/vendor/bytelyst/react-auth/package.json @@ -0,0 +1,37 @@ +{ + "name": "@bytelyst/react-auth", + "version": "0.1.1", + "type": "module", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "peerDependencies": { + "react": ">=18.0.0" + }, + "dependencies": { + "@bytelyst/api-client": "file:../api-client" + }, + "devDependencies": { + "@testing-library/react": "^16.3.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "happy-dom": "^18.0.1", + "react": "^19.2.4", + "react-dom": "^19.2.4" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} \ No newline at end of file diff --git a/vendor/bytelyst/telemetry-client/package.json b/vendor/bytelyst/telemetry-client/package.json new file mode 100644 index 0000000..ae6b284 --- /dev/null +++ b/vendor/bytelyst/telemetry-client/package.json @@ -0,0 +1,24 @@ +{ + "name": "@bytelyst/telemetry-client", + "version": "0.1.0", + "type": "module", + "description": "Browser/React Native-safe telemetry client for platform-service", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "vitest run --pool forks" + }, + "publishConfig": { + "registry": "https://gitea.bytelyst.com/api/packages/ByteLyst/npm/" + } +} diff --git a/web/nginx.conf b/web/nginx.conf new file mode 100644 index 0000000..37a24fd --- /dev/null +++ b/web/nginx.conf @@ -0,0 +1,23 @@ +server { + listen 3048; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # Gzip static assets + gzip on; + gzip_types text/plain text/css application/javascript application/json image/svg+xml; + gzip_min_length 1024; + + # Long-lived cache for hashed assets; no-cache for entry points + location ~* \.(js|css|woff2?|png|jpg|svg|ico)$ { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # SPA fallback — all routes serve index.html + location / { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + try_files $uri $uri/ /index.html; + } +}