From e31e9b2a4b1c9cbe6f44dba33c42a32442262e95 Mon Sep 17 00:00:00 2001 From: edobusy Date: Fri, 20 Mar 2026 15:16:44 +1100 Subject: [PATCH] Add .gitattributes and .editorconfig for cross-platform consistency --- .claude/settings.local.json | 17 +++- .dockerignore | 12 +++ .editorconfig | 12 +++ .gitattributes | 18 +++++ GETTING_STARTED.md | 87 ++++++++++++++++++++ apps/sidekick-api/scripts/start-local-db.sh | 6 ++ docker-compose.yml | 44 ++++++++++ docker/Dockerfile | 8 ++ docker/entrypoint.sh | 63 +++++++++++++++ package-lock.json | 90 +++++++-------------- package.json | 11 ++- 11 files changed, 303 insertions(+), 65 deletions(-) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile create mode 100644 docker/entrypoint.sh diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2d9c3c1b..aa4e2346 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -74,7 +74,22 @@ "Bash(npx playwright test:*)", "Bash(sort:*)", "Bash(npx playwright install:*)", - "Bash(ls:*)" + "Bash(ls:*)", + "Bash(grep -r '\"\"clean\"\"' --include=\"package.json\" .)", + "Bash(grep -r '\"\"clean\"\"' --include=\"package.json\" . --exclude-dir=node_modules)", + "Bash(find /c/Users/User/Documents/Projects/nimble/apps/portal/app /c/Users/User/Documents/Projects/nimble/apps/portal/lib /c/Users/User/Documents/Projects/nimble/apps/portal/components -type f \\\\\\(-name *.ts -o -name *.tsx \\\\\\))", + "Bash(git:*)", + "Bash(echo \"EXIT:$?\")", + "Bash(docker compose:*)", + "Bash(bash -n /c/Users/User/Documents/Projects/nimble/.devcontainer/setup.sh)", + "Bash(bash -n /c/Users/User/Documents/Projects/nimble/apps/sidekick-api/scripts/start-local-db.sh)", + "Bash(DEVCONTAINER=true bash /c/Users/User/Documents/Projects/nimble/apps/sidekick-api/scripts/start-local-db.sh)", + "Bash(echo \"Exit code: $?\")", + "Bash(docker build:*)", + "Bash(docker rmi:*)", + "Bash(wsl --list --verbose)", + "Bash(wsl:*)", + "Bash(docker ps:*)" ], "deny": [], "ask": [], diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..d0fedbd4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +.next +out +dist +.turbo +.git +.claude +.env +.env.* +!.env.example +test-results +playwright-report diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..025b86ff --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..82a9d3b7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Enforce LF line endings for all text files +* text=auto eol=lf + +# Ensure shell scripts always use LF (critical for bash execution) +*.sh text eol=lf +.husky/* text eol=lf + +# Binary files +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.woff binary +*.woff2 binary +*.ttf binary +*.eot binary diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index 1231f19a..80593d33 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -2,6 +2,93 @@ This guide will help you set up and run the Sidekick monorepo locally. +There are two ways to set up your development environment: + +- **[Native Setup](#prerequisites)** (macOS/Linux) — Install Node.js, Docker, and dependencies directly on your machine +- **[Docker Setup](#docker-development-setup)** (Windows / Cross-platform) — Run everything inside Docker containers with a single command + +--- + +## Docker Development Setup + +Use this setup if you're on **Windows** or prefer a containerized environment. All you need is [Docker Desktop](https://www.docker.com/products/docker-desktop/) — no Node.js, PostgreSQL, or other dependencies required on your machine. + +### Prerequisites + +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) installed and running + +### Quick Start + +```bash +# Clone the repository +git clone +cd nimble + +# Start the development environment (first run takes a few minutes) +npm run docker:up + +# Start the web app +npm run docker:dev:sidekick +``` + +Open http://localhost:3000 in your browser. + +The first `docker:up` automatically handles npm install, shared package builds, database creation, and Prisma migrations. Subsequent starts reuse cached dependencies and are much faster. + +### Available Docker Commands + +| Command | Description | +|---------|-------------| +| `npm run docker:up` | Start containers (runs setup on first boot) | +| `npm run docker:down` | Stop containers | +| `npm run docker:shell` | Open a bash shell inside the container | +| `npm run docker:dev` | Start all apps | +| `npm run docker:dev:sidekick` | Start the web app only (port 3000) | +| `npm run docker:dev:api` | Start the API server only (port 3001) | +| `npm run docker:test` | Run unit tests | +| `npm run docker:lint` | Run ESLint | +| `npm run docker:typecheck` | Run TypeScript type checking | + +For any command not listed above, use the shell: + +```bash +npm run docker:shell +# Then run any command inside the container, e.g.: +npm run dev:vault +npx turbo run test:e2e --filter=@nimble/sidekick +``` + +### IDE Support (TypeScript Intellisense) + +The Docker setup uses a separate `node_modules` volume inside the container, so your host machine won't have `node_modules` by default. To get TypeScript autocomplete and go-to-definition working in your editor, run `npm install` locally as well: + +```bash +npm install +``` + +This gives your editor access to type definitions. The container and host `node_modules` are independent and don't interfere with each other. + +### Resetting the Environment + +```bash +# Stop containers and remove all data (node_modules, database) +docker compose down -v + +# Start fresh +npm run docker:up +``` + +### How It Works + +- **`docker-compose.yml`** defines two services: a Node.js 20 app container and a PostgreSQL 15 database +- **`docker/entrypoint.sh`** runs automatically on first start: installs dependencies, builds shared packages, copies `.env.example` files, and runs database migrations +- Source code is bind-mounted from your host, so edits are reflected immediately +- The `DEVCONTAINER=true` environment variable tells the existing `start-local-db.sh` script to skip its Docker logic (the database is already provided by Docker Compose) + +--- + +## Native Setup (macOS/Linux) + ## Prerequisites Before you begin, ensure you have the following installed on your system: diff --git a/apps/sidekick-api/scripts/start-local-db.sh b/apps/sidekick-api/scripts/start-local-db.sh index 7fc15f42..eac71c45 100755 --- a/apps/sidekick-api/scripts/start-local-db.sh +++ b/apps/sidekick-api/scripts/start-local-db.sh @@ -9,6 +9,12 @@ if [ "$NODE_ENV" = "production" ] || [ "$CI" = "true" ]; then exit 0 fi +# In a dev container, PostgreSQL is provided by Docker Compose +if [ "$DEVCONTAINER" = "true" ]; then + echo "✅ Dev container detected — PostgreSQL is managed by Docker Compose" + exit 0 +fi + # Check if Docker is running if ! docker info > /dev/null 2>&1; then echo "Docker is not running. Please start Docker Desktop." diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..b73c1720 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,44 @@ +services: + app: + build: + context: . + dockerfile: docker/Dockerfile + volumes: + - .:/workspace:cached + - node_modules:/workspace/node_modules + entrypoint: ["/workspace/docker/entrypoint.sh"] + command: ["sleep", "infinity"] + depends_on: + db: + condition: service_healthy + environment: + DEVCONTAINER: "true" + DATABASE_URL: "postgresql://postgres:nimblelocal123@db:5432/nimbledb?schema=public" + SESSION_SECRET: "dev-session-secret-not-for-production-1234567890" + ports: + - "3000:3000" + - "3001:3001" + - "3002:3002" + - "4000:4000" + - "4321:4321" + + db: + image: postgres:15-alpine + restart: unless-stopped + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: nimblelocal123 + POSTGRES_DB: nimbledb + volumes: + - postgres-data:/var/lib/postgresql/data + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + node_modules: + postgres-data: diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..3a82cb78 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,8 @@ +FROM node:20-bookworm-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + curl \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /workspace diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000..9075213e --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,63 @@ +#!/bin/bash +set -e + +echo "=== Nimble Dev Environment Setup ===" + +# Fix execute bits (Windows checkout strips them) +find /workspace -name "*.sh" -not -path "*/node_modules/*" -exec chmod +x {} \; +chmod +x /workspace/.husky/pre-commit /workspace/.husky/pre-push 2>/dev/null || true + +# Copy .env.example files if .env doesn't exist +copy_env() { + local src="$1" dest="$2" + if [ ! -f "$dest" ]; then + echo " Creating $dest from example..." + cp "$src" "$dest" + fi +} + +copy_env apps/sidekick-api/.env.example apps/sidekick-api/.env +copy_env apps/sidekick/.env.example apps/sidekick/.env.local +copy_env apps/portal/.env.example apps/portal/.env.local +copy_env apps/vault/.env.example apps/vault/.env +copy_env apps/discord/.env.example apps/discord/.env + +# Install dependencies +echo "Installing npm dependencies..." +npm install + +# Wait for database to be ready +echo "Waiting for database..." +for i in $(seq 1 30); do + if node -e " + const net = require('net'); + const s = net.createConnection({host:'db',port:5432}); + s.on('connect',()=>{s.end();process.exit(0)}); + s.on('error',()=>process.exit(1)); + " 2>/dev/null; then + echo " Database is ready" + break + fi + if [ "$i" -eq 30 ]; then + echo " ERROR: Database not reachable after 30s" + exit 1 + fi + sleep 1 +done + +# Build all shared packages so apps can import them +echo "Building shared packages..." +npx turbo build --filter='./packages/*' + +# Generate Prisma client and run migrations +echo "Setting up database..." +cd apps/sidekick-api +npx prisma generate +npx prisma migrate deploy +cd /workspace + +echo "" +echo "=== Setup complete! ===" +echo "" + +exec "$@" diff --git a/package-lock.json b/package-lock.json index 32425630..d1556fc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1845,6 +1845,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -1862,6 +1863,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -1879,6 +1881,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -1896,6 +1899,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -1913,6 +1917,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -1930,6 +1935,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -1947,6 +1953,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -1964,6 +1971,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -1981,6 +1989,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -1998,6 +2007,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -2015,6 +2025,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -2032,6 +2043,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -2049,6 +2061,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -2066,6 +2079,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -2083,6 +2097,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -2100,6 +2115,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -2117,6 +2133,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -2134,6 +2151,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -2151,6 +2169,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -2168,6 +2187,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -2185,6 +2205,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -2202,6 +2223,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -2219,6 +2241,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -2236,6 +2259,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -2253,6 +2277,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -2270,6 +2295,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -7787,9 +7813,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7804,9 +7827,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7821,9 +7841,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7838,9 +7855,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7855,9 +7869,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7872,9 +7883,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7889,9 +7897,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7906,9 +7911,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7923,9 +7925,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7940,9 +7939,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7957,9 +7953,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7974,9 +7967,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7991,9 +7981,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8347,9 +8334,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8366,9 +8350,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -8385,9 +8366,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -8404,9 +8382,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -15348,9 +15323,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -15371,9 +15343,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -15394,9 +15363,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -15417,9 +15383,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -20904,6 +20867,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } diff --git a/package.json b/package.json index 80ef37b8..e4eb853b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,16 @@ "test": "turbo test", "test:watch": "turbo test:watch", "test:ui": "turbo test:ui", - "prepare": "npx husky || true" + "prepare": "npx husky || true", + "docker:up": "docker compose up -d", + "docker:down": "docker compose down", + "docker:shell": "docker compose exec app bash", + "docker:dev": "docker compose exec app npm run dev", + "docker:dev:sidekick": "docker compose exec app npm run dev:sidekick", + "docker:dev:api": "docker compose exec app npm run dev:api", + "docker:test": "docker compose exec app npm run test", + "docker:lint": "docker compose exec app npm run lint", + "docker:typecheck": "docker compose exec app npm run typecheck" }, "devDependencies": { "husky": "^9.1.7",