diff --git a/.docker/client.Dockerfile b/.docker/client.Dockerfile new file mode 100644 index 0000000..23b04c0 --- /dev/null +++ b/.docker/client.Dockerfile @@ -0,0 +1,55 @@ +FROM node:24-bookworm-slim AS development + +RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +# Layer 1: workspace config + lockfile +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ + +# Layer 2: package.json manifests only +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests + +# Layer 3: install (skip native addon builds — canvas is server-only) +RUN pnpm install --frozen-lockfile --ignore-scripts + +# Layer 4: source code +COPY packages/ ./packages/ +COPY apps/client/ ./apps/client/ + +EXPOSE 5173 + +WORKDIR /app/apps/client +CMD ["pnpm", "dev"] + +# ---- Production build ---- +FROM node:24-bookworm-slim AS builder + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests +RUN pnpm install --frozen-lockfile --ignore-scripts + +COPY packages/ ./packages/ +COPY apps/client/ ./apps/client/ + +WORKDIR /app/apps/client +RUN pnpm build + +# ---- Production serve ---- +FROM nginx:alpine AS production + +COPY --from=builder /app/apps/client/dist /usr/share/nginx/html +COPY apps/client/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/.docker/copy-manifests.sh b/.docker/copy-manifests.sh new file mode 100755 index 0000000..5f2ad70 --- /dev/null +++ b/.docker/copy-manifests.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Copies all workspace package.json files into a staging directory +# with encoded filenames (path separators → __) so Docker COPY can +# grab them in one layer. The Dockerfile reverses the encoding. +# +# Usage: .docker/copy-manifests.sh + +set -e + +OUT="${1:-.docker/.manifests}" +rm -rf "$OUT" +mkdir -p "$OUT" + +for f in apps/*/package.json packages/*/package.json; do + [ -f "$f" ] || continue + encoded=$(echo "$f" | sed 's|/|__|g') + cp "$f" "$OUT/$encoded" +done + +echo "Copied $(ls "$OUT" | wc -l | tr -d ' ') manifests to $OUT" diff --git a/.docker/docs.Dockerfile b/.docker/docs.Dockerfile new file mode 100644 index 0000000..674bcb4 --- /dev/null +++ b/.docker/docs.Dockerfile @@ -0,0 +1,55 @@ +FROM node:24-bookworm-slim AS development + +RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +# Layer 1: workspace config + lockfile +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ + +# Layer 2: package.json manifests only +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests + +# Layer 3: install (skip native addon builds — canvas is server-only) +RUN pnpm install --frozen-lockfile --ignore-scripts + +# Layer 4: source code +COPY packages/ ./packages/ +COPY apps/docs/ ./apps/docs/ + +EXPOSE 3001 + +WORKDIR /app/apps/docs +CMD ["pnpm", "dev"] + +# ---- Production build ---- +FROM node:24-bookworm-slim AS builder + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests +RUN pnpm install --frozen-lockfile --ignore-scripts + +COPY packages/ ./packages/ +COPY apps/docs/ ./apps/docs/ + +WORKDIR /app/apps/docs +RUN pnpm build + +# ---- Production serve ---- +FROM nginx:alpine AS production + +COPY --from=builder /app/apps/docs/dist /usr/share/nginx/html +COPY apps/docs/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/.docker/patch-vendor-links.sh b/.docker/patch-vendor-links.sh new file mode 100644 index 0000000..7ad66e3 --- /dev/null +++ b/.docker/patch-vendor-links.sh @@ -0,0 +1,60 @@ +#!/bin/sh +# Patches link: paths in package.json to point to vendored copies at +# $ROOT/vendor/ so pnpm install can resolve them with --frozen-lockfile. +# +# Also creates a /riotbyte/project-q/packages/ directory tree that +# mirrors the layout pnpm's lockfile expects (relative link: targets +# resolve to /riotbyte/... inside Docker because the workspace root +# is at /app which is only 1 level deep). +# +# Usage: patch-vendor-links.sh [workspace-root] + +set -e + +ROOT="${1:-.}" + +# Step 1: Patch package.json specifiers (link:host-path → link:$ROOT/vendor/*) +# pnpm validates specifiers match package.json during --frozen-lockfile +find "$ROOT" -name 'package.json' -not -path '*/node_modules/*' | while read -r f; do + if grep -q '"link:.*project-q' "$f" 2>/dev/null; then + sed -i \ + -e "s|\"link:[^\"]*project-q/packages/core\"|\"link:$ROOT/vendor/project-q-core\"|g" \ + -e "s|\"link:[^\"]*project-q/packages/framework/nest\"|\"link:$ROOT/vendor/project-q-nestjs\"|g" \ + -e "s|\"link:[^\"]*project-q/packages/transport/postgres\"|\"link:$ROOT/vendor/project-q-transport-postgres\"|g" \ + "$f" + echo "Patched $f" + fi +done + +# Step 2: Patch lockfile specifiers to match the package.json changes +# Only patch absolute specifier lines (link:/...), leave relative +# version lines untouched — they resolve via mount points below. +LOCKFILE="$ROOT/pnpm-lock.yaml" +if [ -f "$LOCKFILE" ] && grep -q 'project-q' "$LOCKFILE" 2>/dev/null; then + sed -i \ + -e "s|link:/[^ ]*project-q/packages/core|link:$ROOT/vendor/project-q-core|g" \ + -e "s|link:/[^ ]*project-q/packages/framework/nest|link:$ROOT/vendor/project-q-nestjs|g" \ + -e "s|link:/[^ ]*project-q/packages/transport/postgres|link:$ROOT/vendor/project-q-transport-postgres|g" \ + "$LOCKFILE" + echo "Patched $LOCKFILE" +fi + +# Step 3: Create mount points so pnpm's relative symlinks resolve correctly. +# Inside Docker, relative link: paths from the lockfile resolve to +# /riotbyte/project-q/packages/* (because the workspace is at /app). +# We create symlinks there pointing to the vendored copies inside the +# workspace tree (at $ROOT/vendor/). +mkdir -p /riotbyte/project-q/packages/framework /riotbyte/project-q/packages/transport +ln -sfn "$ROOT/vendor/project-q-core" /riotbyte/project-q/packages/core +ln -sfn "$ROOT/vendor/project-q-nestjs" /riotbyte/project-q/packages/framework/nest +ln -sfn "$ROOT/vendor/project-q-transport-postgres" /riotbyte/project-q/packages/transport/postgres +echo "Created mount points at /riotbyte/project-q/packages/" + +# Step 4: Create root-level node_modules symlinks so vendored packages +# can resolve each other (e.g. transport-postgres depends on core). +# pnpm only creates per-workspace symlinks for link: deps, not at root. +mkdir -p "$ROOT/node_modules/@riotbyte" +ln -sfn "$ROOT/vendor/project-q-core" "$ROOT/node_modules/@riotbyte/project-q-core" +ln -sfn "$ROOT/vendor/project-q-nestjs" "$ROOT/node_modules/@riotbyte/project-q-nestjs" +ln -sfn "$ROOT/vendor/project-q-transport-postgres" "$ROOT/node_modules/@riotbyte/project-q-transport-postgres" +echo "Created root node_modules symlinks for project-q" diff --git a/.docker/restore-manifests.sh b/.docker/restore-manifests.sh new file mode 100755 index 0000000..85397ba --- /dev/null +++ b/.docker/restore-manifests.sh @@ -0,0 +1,16 @@ +#!/bin/sh +# Restores encoded package.json files back to their original paths. +# Reverses the encoding from copy-manifests.sh (__ → /). +# +# Usage: restore-manifests.sh + +set -e + +DIR="${1:-/tmp/manifests}" + +for f in "$DIR"/*; do + [ -f "$f" ] || continue + original=$(basename "$f" | sed 's|__|/|g') + mkdir -p "$(dirname "$original")" + mv "$f" "$original" +done diff --git a/.docker/server.Dockerfile b/.docker/server.Dockerfile new file mode 100644 index 0000000..64b4ad8 --- /dev/null +++ b/.docker/server.Dockerfile @@ -0,0 +1,133 @@ +FROM node:24-bookworm-slim AS development + +# canvas (node-canvas) native build deps +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl openssl \ + build-essential python3 pkg-config \ + libpixman-1-dev libcairo2-dev libpango1.0-dev \ + libjpeg-dev libgif-dev librsvg2-dev \ + && rm -rf /var/lib/apt/lists/* +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +# Layer 1: workspace config + lockfile (rarely changes) +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ + +# Layer 2: package.json manifests only (changes when deps change) +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests + +# Layer 2b: vendor project-q packages (from additional build context) +COPY --from=project-q packages/core/package.json ./vendor/project-q-core/package.json +COPY --from=project-q packages/core/dist/ ./vendor/project-q-core/dist/ +COPY --from=project-q packages/framework/nest/package.json ./vendor/project-q-nestjs/package.json +COPY --from=project-q packages/framework/nest/dist/ ./vendor/project-q-nestjs/dist/ +COPY --from=project-q packages/transport/postgres/package.json ./vendor/project-q-transport-postgres/package.json +COPY --from=project-q packages/transport/postgres/dist/ ./vendor/project-q-transport-postgres/dist/ + +# Patch link: → file: for project-q packages +COPY .docker/patch-vendor-links.sh /tmp/ +RUN sh /tmp/patch-vendor-links.sh /app + +# Layer 3: install (lockfile was patched above to match vendored paths) +RUN NPM_CONFIG_SHAMEFULLY_HOIST=true pnpm install --frozen-lockfile + +# Layer 4: source code (changes frequently) +COPY packages/ ./packages/ +COPY apps/server/ ./apps/server/ + +# Patch server package.json too (COPY overwrote the patched version) +RUN sh /tmp/patch-vendor-links.sh /app + +# Generate Prisma client +WORKDIR /app/apps/server +RUN pnpm exec prisma generate + +EXPOSE 3000 + +CMD ["pnpm", "dev"] + +# ---- Production build ---- +FROM node:24-bookworm-slim AS builder + +RUN apt-get update && apt-get install -y --no-install-recommends \ + openssl build-essential python3 pkg-config \ + libpixman-1-dev libcairo2-dev libpango1.0-dev \ + libjpeg-dev libgif-dev librsvg2-dev \ + && rm -rf /var/lib/apt/lists/* +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests + +COPY --from=project-q packages/core/package.json ./vendor/project-q-core/package.json +COPY --from=project-q packages/core/dist/ ./vendor/project-q-core/dist/ +COPY --from=project-q packages/framework/nest/package.json ./vendor/project-q-nestjs/package.json +COPY --from=project-q packages/framework/nest/dist/ ./vendor/project-q-nestjs/dist/ +COPY --from=project-q packages/transport/postgres/package.json ./vendor/project-q-transport-postgres/package.json +COPY --from=project-q packages/transport/postgres/dist/ ./vendor/project-q-transport-postgres/dist/ + +COPY .docker/patch-vendor-links.sh /tmp/ +RUN sh /tmp/patch-vendor-links.sh /app + +RUN NPM_CONFIG_SHAMEFULLY_HOIST=true pnpm install --frozen-lockfile + +COPY packages/ ./packages/ +COPY apps/server/ ./apps/server/ +RUN sh /tmp/patch-vendor-links.sh /app + +WORKDIR /app/apps/server +RUN pnpm exec prisma generate +WORKDIR /app +RUN pnpm build + +# ---- Production runtime ---- +FROM node:24-bookworm-slim AS production + +# canvas runtime libs (no build tools — prebuilt binaries from builder) +RUN apt-get update && apt-get install -y --no-install-recommends \ + openssl curl \ + libcairo2 libpango-1.0-0 libpangocairo-1.0-0 \ + libjpeg62-turbo libgif7 librsvg2-2 libpixman-1-0 \ + && rm -rf /var/lib/apt/lists/* +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests + +COPY --from=project-q packages/core/package.json ./vendor/project-q-core/package.json +COPY --from=project-q packages/core/dist/ ./vendor/project-q-core/dist/ +COPY --from=project-q packages/framework/nest/package.json ./vendor/project-q-nestjs/package.json +COPY --from=project-q packages/framework/nest/dist/ ./vendor/project-q-nestjs/dist/ +COPY --from=project-q packages/transport/postgres/package.json ./vendor/project-q-transport-postgres/package.json +COPY --from=project-q packages/transport/postgres/dist/ ./vendor/project-q-transport-postgres/dist/ + +COPY .docker/patch-vendor-links.sh /tmp/ +RUN sh /tmp/patch-vendor-links.sh /app + +RUN NPM_CONFIG_SHAMEFULLY_HOIST=true pnpm install --frozen-lockfile --prod + +# Copy Prisma schema + generated client +COPY apps/server/prisma/ ./apps/server/prisma/ +COPY --from=builder /app/apps/server/node_modules/.prisma/ ./apps/server/node_modules/.prisma/ +COPY --from=builder /app/node_modules/.prisma/ ./node_modules/.prisma/ + +# Copy compiled output +COPY --from=builder /app/apps/server/dist/ ./apps/server/dist/ +COPY --from=builder /app/packages/*/dist/ ./packages/ + +ENV NODE_ENV=production +EXPOSE 3000 + +WORKDIR /app/apps/server +CMD ["node", "dist/main.js"] diff --git a/.docker/worker.Dockerfile b/.docker/worker.Dockerfile new file mode 100644 index 0000000..b58cfc4 --- /dev/null +++ b/.docker/worker.Dockerfile @@ -0,0 +1,117 @@ +FROM node:24-bookworm-slim AS development + +RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/* +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +# Layer 1: workspace config + lockfile +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ + +# Layer 2: package.json manifests only +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests + +# Layer 2b: vendor project-q packages (inside workspace tree so Node +# module resolution walks up to /app/node_modules) +COPY --from=project-q packages/core/package.json ./vendor/project-q-core/package.json +COPY --from=project-q packages/core/dist/ ./vendor/project-q-core/dist/ +COPY --from=project-q packages/framework/nest/package.json ./vendor/project-q-nestjs/package.json +COPY --from=project-q packages/framework/nest/dist/ ./vendor/project-q-nestjs/dist/ +COPY --from=project-q packages/transport/postgres/package.json ./vendor/project-q-transport-postgres/package.json +COPY --from=project-q packages/transport/postgres/dist/ ./vendor/project-q-transport-postgres/dist/ + +# Patch link: specifiers + create mount points for lockfile relative paths +COPY .docker/patch-vendor-links.sh /tmp/ +RUN sh /tmp/patch-vendor-links.sh /app + +# Layer 3: install (lockfile was patched above to match vendored paths) +# shamefully-hoist makes all deps available at /app/node_modules/ so +# vendored project-q packages (linked via link: protocol) can resolve +# their transitive deps (uuid, zod, pg) through Node's upward walk. +RUN NPM_CONFIG_SHAMEFULLY_HOIST=true pnpm install --frozen-lockfile + +# Layer 4: source code +COPY packages/tsconfig/ ./packages/tsconfig/ +COPY apps/worker/ ./apps/worker/ +RUN sh /tmp/patch-vendor-links.sh /app + +# Install Chromium + system deps via Playwright +RUN cd apps/worker && pnpm exec playwright install --with-deps chromium + +WORKDIR /app/apps/worker + +CMD ["pnpm", "dev"] + +# ---- Production build ---- +FROM node:24-bookworm-slim AS builder + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests + +COPY --from=project-q packages/core/package.json ./vendor/project-q-core/package.json +COPY --from=project-q packages/core/dist/ ./vendor/project-q-core/dist/ +COPY --from=project-q packages/framework/nest/package.json ./vendor/project-q-nestjs/package.json +COPY --from=project-q packages/framework/nest/dist/ ./vendor/project-q-nestjs/dist/ +COPY --from=project-q packages/transport/postgres/package.json ./vendor/project-q-transport-postgres/package.json +COPY --from=project-q packages/transport/postgres/dist/ ./vendor/project-q-transport-postgres/dist/ + +COPY .docker/patch-vendor-links.sh /tmp/ +RUN sh /tmp/patch-vendor-links.sh /app + +# shamefully-hoist makes all deps available at /app/node_modules/ so +# vendored project-q packages (linked via link: protocol) can resolve +# their transitive deps (uuid, zod, pg) through Node's upward walk. +RUN NPM_CONFIG_SHAMEFULLY_HOIST=true pnpm install --frozen-lockfile + +COPY packages/tsconfig/ ./packages/tsconfig/ +COPY apps/worker/ ./apps/worker/ +RUN sh /tmp/patch-vendor-links.sh /app + +WORKDIR /app/apps/worker +RUN pnpm build + +# ---- Production runtime ---- +FROM node:24-bookworm-slim AS production + +RUN corepack enable && corepack prepare pnpm@latest --activate + +WORKDIR /app + +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ +COPY .docker/.manifests/ /tmp/manifests/ +COPY .docker/restore-manifests.sh /tmp/ +RUN sh /tmp/restore-manifests.sh /tmp/manifests + +COPY --from=project-q packages/core/package.json ./vendor/project-q-core/package.json +COPY --from=project-q packages/core/dist/ ./vendor/project-q-core/dist/ +COPY --from=project-q packages/framework/nest/package.json ./vendor/project-q-nestjs/package.json +COPY --from=project-q packages/framework/nest/dist/ ./vendor/project-q-nestjs/dist/ +COPY --from=project-q packages/transport/postgres/package.json ./vendor/project-q-transport-postgres/package.json +COPY --from=project-q packages/transport/postgres/dist/ ./vendor/project-q-transport-postgres/dist/ + +COPY .docker/patch-vendor-links.sh /tmp/ +RUN sh /tmp/patch-vendor-links.sh /app + +# shamefully-hoist makes all deps available at /app/node_modules/ so +# vendored project-q packages (linked via link: protocol) can resolve +# their transitive deps (uuid, zod, pg) through Node's upward walk. +RUN NPM_CONFIG_SHAMEFULLY_HOIST=true pnpm install --frozen-lockfile --prod + +# Install Chromium + system deps via Playwright +RUN cd apps/worker && pnpm exec playwright install --with-deps chromium + +# Copy compiled output +COPY --from=builder /app/apps/worker/dist/ ./apps/worker/dist/ + +ENV NODE_ENV=production + +WORKDIR /app/apps/worker +CMD ["node", "dist/main.js"] diff --git a/.dockerignore b/.dockerignore index a6af473..14a284a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -29,10 +29,12 @@ Thumbs.db .gitignore # Docker -Dockerfile* docker-compose*.yml .dockerignore +# Keep .docker dir (Dockerfiles + manifests) +!.docker/ + # Logs logs/ *.log diff --git a/.env.example b/.env.example index cfffb93..6f4d43f 100644 --- a/.env.example +++ b/.env.example @@ -58,3 +58,6 @@ MODEL_PATH=/models/mistral-7b-instruct-v0.2.Q4_K_M.gguf # LINKEDIN_CLIENT_ID= # LINKEDIN_CLIENT_SECRET= # LINKEDIN_REDIRECT_URI=http://localhost:5173/auth/linkedin/callback + +# project-q / Worker +# PROJECT_Q_PATH=/path/to/project-q # Only needed for docker compose build diff --git a/.gitignore b/.gitignore index b0c5178..0e755d6 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,12 @@ test-*.py test-*.sh test-prompt.json +# Real-world test fixtures (contain PII) +packages/file-upload/src/__tests__/fixtures/* +!packages/file-upload/src/__tests__/fixtures/.gitkeep +apps/server/test/assets/* +!apps/server/test/assets/.gitkeep + # Coverage reports (outside coverage/) **/coverage-unit/ @@ -88,3 +94,6 @@ docker-compose.nvidia.yml # Local scripts scripts/ + +# Docker manifest staging (generated by .docker/copy-manifests.sh) +.docker/.manifests/ diff --git a/apps/client/Dockerfile b/apps/client/Dockerfile deleted file mode 100644 index decca61..0000000 --- a/apps/client/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -FROM node:24-alpine AS development - -RUN apk add --no-cache curl -RUN corepack enable && corepack prepare pnpm@latest --activate - -WORKDIR /app - -# Copy monorepo files -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ -COPY packages/ ./packages/ -COPY apps/ ./apps/ -COPY scripts/ ./scripts/ - -# Install dependencies -RUN pnpm install --frozen-lockfile - -EXPOSE 5173 - -WORKDIR /app/apps/client -CMD ["pnpm", "dev"] - -# Production build stage -FROM node:24-alpine AS builder - -RUN corepack enable && corepack prepare pnpm@latest --activate - -WORKDIR /app - -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ -COPY packages/ ./packages/ -COPY apps/ ./apps/ - -RUN pnpm install --frozen-lockfile - -COPY apps/client/ ./apps/client/ - -WORKDIR /app/apps/client -RUN pnpm build - -# Production serve stage -FROM nginx:alpine AS production - -COPY --from=builder /app/apps/client/dist /usr/share/nginx/html -COPY apps/client/nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/docs/Dockerfile b/apps/docs/Dockerfile deleted file mode 100644 index 969b33c..0000000 --- a/apps/docs/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -FROM node:24-alpine AS development - -RUN apk add --no-cache curl -RUN corepack enable && corepack prepare pnpm@latest --activate - -WORKDIR /app - -# Copy monorepo files -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ -COPY packages/ ./packages/ -COPY apps/ ./apps/ -COPY scripts/ ./scripts/ - -# Install dependencies -RUN pnpm install --frozen-lockfile - -EXPOSE 3001 - -WORKDIR /app/apps/docs -CMD ["pnpm", "dev"] - -# Production build stage -FROM node:24-alpine AS builder - -RUN corepack enable && corepack prepare pnpm@latest --activate - -WORKDIR /app - -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ -COPY packages/ ./packages/ -COPY apps/ ./apps/ - -RUN pnpm install --frozen-lockfile - -COPY apps/docs/ ./apps/docs/ - -WORKDIR /app/apps/docs -RUN pnpm build - -# Production serve stage -FROM nginx:alpine AS production - -COPY --from=builder /app/apps/docs/dist /usr/share/nginx/html -COPY apps/docs/nginx.conf /etc/nginx/conf.d/default.conf - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile deleted file mode 100644 index d6c9739..0000000 --- a/apps/server/Dockerfile +++ /dev/null @@ -1,78 +0,0 @@ -FROM node:24-alpine AS development - -RUN apk add --no-cache curl openssl -RUN corepack enable && corepack prepare pnpm@latest --activate - -WORKDIR /app - -# Copy root package files -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ - -# Copy all packages and apps -COPY packages/ ./packages/ -COPY apps/ ./apps/ - -# Copy health check scripts -COPY scripts/ ./scripts/ - -# Install dependencies -RUN pnpm install --frozen-lockfile - -# Generate Prisma client -WORKDIR /app/apps/server -RUN pnpm exec prisma generate -WORKDIR /app - -EXPOSE 3000 - -WORKDIR /app/apps/server -CMD ["pnpm", "dev"] - -# ---- Production build ---- -FROM node:24-alpine AS builder - -RUN apk add --no-cache openssl -RUN corepack enable && corepack prepare pnpm@latest --activate - -WORKDIR /app - -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ -COPY packages/ ./packages/ -COPY apps/ ./apps/ - -RUN pnpm install --frozen-lockfile - -WORKDIR /app/apps/server -RUN pnpm exec prisma generate -WORKDIR /app - -RUN pnpm build - -# ---- Production runtime ---- -FROM node:24-alpine AS production - -RUN apk add --no-cache openssl curl -RUN corepack enable && corepack prepare pnpm@latest --activate - -WORKDIR /app - -COPY package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./ -COPY packages/ ./packages/ -COPY apps/server/package.json ./apps/server/ - -RUN pnpm install --frozen-lockfile --prod - -# Copy Prisma schema + generated client -COPY apps/server/prisma/ ./apps/server/prisma/ -COPY --from=builder /app/apps/server/node_modules/.prisma/ ./apps/server/node_modules/.prisma/ -COPY --from=builder /app/node_modules/.prisma/ ./node_modules/.prisma/ - -# Copy compiled output -COPY --from=builder /app/apps/server/dist/ ./apps/server/dist/ -COPY --from=builder /app/packages/*/dist/ ./packages/ - -ENV NODE_ENV=production -EXPOSE 3000 - -WORKDIR /app/apps/server -CMD ["node", "dist/main.js"] diff --git a/docker-compose.yml b/docker-compose.yml index 0122920..4b85efd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,10 @@ services: server: build: context: . - dockerfile: apps/server/Dockerfile + dockerfile: .docker/server.Dockerfile + target: development + additional_contexts: + project-q: ${PROJECT_Q_PATH:?Set PROJECT_Q_PATH to your local project-q checkout} environment: PORT: ${SERVER_PORT:-3000} JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-here} @@ -30,9 +33,10 @@ services: POSTGRES_DB: ${POSTGRES_DB:-cv} ENCRYPTION_KEY: ${ENCRYPTION_KEY:-dev-encryption-key-32-chars-long!} RESEND_API_KEY: ${RESEND_API_KEY:-} - LLAMA_URL: ${LLAMA_URL:-http://llama:8080} + LLAMA_URL: ${LLAMA_URL:-http://host.docker.internal:8080} AI_TIMEOUT: ${AI_TIMEOUT:-300000} - AI_MAX_TOKENS: ${AI_MAX_TOKENS:-4096} + AI_MAX_TOKENS: ${AI_MAX_TOKENS:-8192} + PDF_OUTPUT_DIR: /app/pdf-output depends_on: db: condition: service_healthy @@ -41,9 +45,14 @@ services: volumes: - ./apps/server/src:/app/apps/server/src - ./apps/server/prisma:/app/apps/server/prisma - - ./packages:/app/packages - - pnpm-cache:/root/.local/share/pnpm - command: sh -c "cd /app/apps/server && pnpm prisma:deploy && pnpm dev" + - ./packages/ai-parser/src:/app/packages/ai-parser/src + - ./packages/ai-provider/src:/app/packages/ai-provider/src + - ./packages/auth/src:/app/packages/auth/src + - ./packages/cv-renderer/src:/app/packages/cv-renderer/src + - ./packages/file-upload/src:/app/packages/file-upload/src + - ./packages/system/src:/app/packages/system/src + - worker-output:/app/pdf-output:ro + command: sh -c "cd /app/apps/server && pnpm prisma generate && pnpm prisma:deploy && pnpm dev" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 15s @@ -54,7 +63,7 @@ services: client: build: context: . - dockerfile: apps/client/Dockerfile + dockerfile: .docker/client.Dockerfile target: development environment: VITE_SERVER_URL: ${VITE_SERVER_URL:-http://localhost:3000} @@ -68,8 +77,9 @@ services: - "${CLIENT_PORT:-5173}:5173" volumes: - ./apps/client/src:/app/apps/client/src - - ./packages:/app/packages - - pnpm-cache:/root/.local/share/pnpm + - ./packages/routing/src:/app/packages/routing/src + - ./packages/ui/src:/app/packages/ui/src + - ./packages/system/src:/app/packages/system/src command: sh -c "cd /app/apps/client && (pnpm codegen || true) && pnpm dev" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5173"] @@ -78,10 +88,38 @@ services: retries: 3 start_period: 20s + worker: + build: + context: . + dockerfile: .docker/worker.Dockerfile + target: development + additional_contexts: + project-q: ${PROJECT_Q_PATH:?Set PROJECT_Q_PATH to your local project-q checkout} + environment: + DATABASE_URL: ${DATABASE_URL:-postgresql://cv:cv@db:5432/cv} + QUEUE_SCHEMA: ${QUEUE_SCHEMA:-queue} + QUEUE_NAME: ${QUEUE_NAME:-default} + POLL_INTERVAL_MS: ${POLL_INTERVAL_MS:-1000} + PDF_OUTPUT_DIR: /app/pdf-output + HEARTBEAT_FILE_PATH: /tmp/worker-heartbeat + depends_on: + db: + condition: service_healthy + volumes: + - ./apps/worker/src:/app/apps/worker/src + - worker-output:/app/pdf-output + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "find /tmp/worker-heartbeat -mmin -1 | grep -q ."] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + docs: build: context: . - dockerfile: apps/docs/Dockerfile + dockerfile: .docker/docs.Dockerfile target: development environment: VITE_CLIENT_URL: ${VITE_CLIENT_URL:-http://localhost:5173} @@ -91,8 +129,9 @@ services: volumes: - ./apps/docs/src:/app/apps/docs/src - ./apps/docs/content:/app/apps/docs/content - - ./packages:/app/packages - - pnpm-cache:/root/.local/share/pnpm + - ./packages/routing/src:/app/packages/routing/src + - ./packages/ui/src:/app/packages/ui/src + - ./packages/system/src:/app/packages/system/src healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3001"] interval: 15s @@ -130,7 +169,7 @@ services: - "${LLAMA_PORT:-8080}:8080" volumes: - ./ai-models:/models - command: -m /models/mistral-7b-instruct-v0.2.Q4_K_M.gguf --port 8080 --host 0.0.0.0 -c 4096 -ngl 0 # -ngl 0 = CPU only + command: -m /models/mistral-7b-instruct-v0.2.Q4_K_M.gguf --port 8080 --host 0.0.0.0 -c 16384 -ngl 0 # -ngl 0 = CPU only depends_on: model-download: condition: service_completed_successfully @@ -143,4 +182,4 @@ services: volumes: db-data: - pnpm-cache: + worker-output: