From 4d1004a058b9134b94ab7cc8390869c6368de9f7 Mon Sep 17 00:00:00 2001 From: kuyacarlo <106532351+kuyacarlo@users.noreply.github.com> Date: Sat, 25 Jul 2026 10:59:09 +0800 Subject: [PATCH 1/2] feat(deploy): uniform per-app Vercel configs (#20) The repo had a single apps/web/vercel.json with hardcoded env plus a legacy builds/routes block on the API, while three Vercel projects (worksight, worksight-api, worksight-docs) each read config from their own Root Directory. Vercel loads exactly one vercel.json per project and never merges a root file with a nested one, so centralization here means uniform per-app configs sharing the same workspace primitives. - apps/web: nextjs preset, pnpm --filter build, telemetry off, no outputDirectory override (preset default is correct) - apps/api: drop legacy builds/routes, which disabled Vercel's install and build steps entirely; output dist - apps/docs: same shape, output .vitepress/dist - remove committed NEXT_PUBLIC_* values incl. the Supabase anon key; env stays dashboard-managed per environment - doc/DEPLOYMENT.md + README: per-project table matching the applied dashboard settings (Root Directory, canary, skip-unaffected), and an honest note that the API still lacks a serverless handler Co-authored-by: Cursor --- README.md | 38 +++-- apps/api/vercel.json | 18 +-- apps/docs/vercel.json | 9 +- apps/web/vercel.json | 12 +- doc/DEPLOYMENT.md | 134 +++++++++++++----- .../2026-07-25-centralize-deployments.md | 52 ++++++- 6 files changed, 175 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index adb5d9f..9bf99b0 100644 --- a/README.md +++ b/README.md @@ -136,24 +136,32 @@ pnpm preview # Preview production build ### Production Environments -- **Web Application**: [worksight.vercel.app](https://worksight.vercel.app) -- **Documentation**: [worksight.github.io](https://worksight.github.io) +- **Web Application** (`@worksight/web`): Vercel โ€” + [worksight.vercel.app](https://worksight.vercel.app) +- **Documentation** (`@worksight/docs`): GitHub Pages (optionally Vercel) +- **API** (`@worksight/api`): Docker (`docker-compose.yml` + `nginx/`) ### Deployment Process -1. **Automatic Deployment**: - - Push to `main` branch triggers production deployment - - Pull requests create preview deployments (web app only) - -2. **Manual Deployment**: - - ```bash - # Trigger GitHub Actions workflow - gh workflow run deploy.yml - ``` - -See [Deployment Guide](./apps/docs/guide/deployment.md) for detailed -instructions. +This is a Turborepo + pnpm monorepo, so each app deploys as its **own** Vercel +project (or non-Vercel target). Vercel loads a single `vercel.json` per project +based on its dashboard **Root Directory** setting: + +- **Web** (`worksight`): Root Directory `apps/web` โ†’ `apps/web/vercel.json`. +- **API** (`worksight-api`): Root Directory `apps/api` โ†’ `apps/api/vercel.json` + (still needs a serverless handler; Docker is the working path today). +- **Docs** (`worksight-docs`): Root Directory `apps/docs` โ†’ + `apps/docs/vercel.json`. + +All three share `pnpm install --frozen-lockfile`, a +`pnpm --filter @worksight/ build` command, production branch `canary`, and +skip-unaffected-project deploys. No environment values are committed to +`vercel.json`. + +Dashboard-only steps (creating projects, setting Root Directory, adding env +vars) cannot be performed by repo files. See the +[Deployment Guide](./doc/DEPLOYMENT.md) for the full setup, including the +required Vercel dashboard configuration. ## ๐Ÿงช Testing diff --git a/apps/api/vercel.json b/apps/api/vercel.json index 7b3f518..18511c9 100644 --- a/apps/api/vercel.json +++ b/apps/api/vercel.json @@ -1,16 +1,6 @@ { - "version": 2, - "builds": [ - { - "src": "dist/main.js", - "use": "@vercel/node" - } - ], - "routes": [ - { - "src": "/(.*)", - "dest": "dist/main.js", - "methods": ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"] - } - ] + "$schema": "https://openapi.vercel.sh/vercel.json", + "installCommand": "pnpm install --frozen-lockfile", + "buildCommand": "pnpm --filter @worksight/api build", + "outputDirectory": "dist" } diff --git a/apps/docs/vercel.json b/apps/docs/vercel.json index 1d6f65e..b346ec9 100644 --- a/apps/docs/vercel.json +++ b/apps/docs/vercel.json @@ -1,10 +1,7 @@ { - "version": 2, + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "vitepress", "installCommand": "pnpm install --frozen-lockfile", "buildCommand": "pnpm --filter @worksight/docs build", - "outputDirectory": ".vitepress/dist", - "framework": "vitepress", - "build": { - "env": {} - } + "outputDirectory": ".vitepress/dist" } diff --git a/apps/web/vercel.json b/apps/web/vercel.json index ce91016..6ee9a19 100644 --- a/apps/web/vercel.json +++ b/apps/web/vercel.json @@ -1,16 +1,8 @@ { - "version": 2, + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "nextjs", "installCommand": "pnpm install --frozen-lockfile", "buildCommand": "pnpm --filter @worksight/web build", - "outputDirectory": ".next", - "framework": "nextjs", - "env": { - "NEXT_PUBLIC_APP_NAME": "WorkSight", - "NEXT_PUBLIC_APP_DESCRIPTION": "Employee Well-being Analytics Platform", - "NEXT_PUBLIC_SUPABASE_URL": "https://ontaynsmofmfvzypfhra.supabase.co", - "NEXT_PUBLIC_SUPABASE_ANON_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9udGF5bnNtb2ZtZnZ6eXBmaHJhIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTQxNTE4NDksImV4cCI6MjA2OTcyNzg0OX0.5x1_4JpVZPtesadh0TI6-eVArLCv-Pnukju1VDNIj14", - "NEXT_PUBLIC_APP_URL": "https://worksight.vercel.app" - }, "build": { "env": { "NEXT_TELEMETRY_DISABLED": "1" diff --git a/doc/DEPLOYMENT.md b/doc/DEPLOYMENT.md index 0b422a1..e49c43f 100644 --- a/doc/DEPLOYMENT.md +++ b/doc/DEPLOYMENT.md @@ -1,36 +1,92 @@ # ๐Ÿš€ WorkSight Deployment Guide -## Quick Deploy to Vercel +WorkSight is a **Turborepo + pnpm workspace** monorepo. There is no single +deploy artifact โ€” each app ships on the platform that fits it: + +| App | Package | Vercel project | Root Directory | Config source of truth | +| ----------- | ----------------- | ---------------- | -------------- | ----------------------- | +| `apps/web` | `@worksight/web` | `worksight` | `apps/web` | `apps/web/vercel.json` | +| `apps/api` | `@worksight/api` | `worksight-api` | `apps/api` | `apps/api/vercel.json` | +| `apps/docs` | `@worksight/docs` | `worksight-docs` | `apps/docs` | `apps/docs/vercel.json` | + +All three Vercel projects are connected to the same GitHub repository +(`4sightorg/worksight`) with production branch **`canary`**, share +`pnpm install --frozen-lockfile`, and have **Include source files outside of the +Root Directory** plus **skip unaffected projects** enabled. The API can +alternatively run under Docker (`docker-compose.yml` + `nginx/`). + +> โš ๏ธ **What repo config can and cannot do.** A `vercel.json` file only +> configures build/routing behavior. It **cannot** create Vercel projects, set a +> project's **Root Directory**, add environment variables to the dashboard, or +> link a Git repo. Those are dashboard/CLI actions โ€” they are called out +> explicitly under +> [Vercel dashboard setup](#vercel-dashboard-setup-one-time-per-project). + +## How Vercel resolves config in this monorepo + +Vercel reads **exactly one** `vercel.json` per project: the one located at the +project's **Root Directory** (a dashboard setting). It does **not** merge a root +`vercel.json` with a nested one. Consequences: + +- One Vercel project builds one output, so web, api, and docs need **separate** + projects. A single root `vercel.json` cannot govern all three. +- "Centralized" here means **uniform, per-app configs** committed next to each + app, all using the same workspace-level primitives โ€” not one shared file. +- Each project's Root Directory therefore points at its app, and that app's + `vercel.json` is the only file Vercel loads for it. + +Every app config uses the same primitives, so behavior is consistent: + +- Install: `pnpm install --frozen-lockfile` +- Build: `pnpm --filter @worksight/ build` (pnpm runs the workspace build + script; Turbo's `dependsOn: ["^build"]` graph builds workspace dependencies + such as `@worksight/common` and `@worksight/assets` first). +- Output: framework default for Next.js; explicit for docs (`.vitepress/dist`) + and api (`dist`). + +Secrets and environment values live in the **dashboard**, not in `vercel.json`. + +## Vercel dashboard setup (one-time, per project) + +These steps **must** be done in the Vercel dashboard or CLI โ€” no repo file can +perform them. + +### Web project (`@worksight/web`) + +1. Import the `4sightorg/worksight` repo as a new Vercel project. +2. **Root Directory โ†’ `apps/web`**, so Vercel loads `apps/web/vercel.json`. +3. Leave **"Include source files outside of the Root Directory in the Build + Step"** enabled so the shared `packages/*` and the workspace lockfile are + available. +4. Framework Preset / Install / Build come from `apps/web/vercel.json`; do not + override them in the dashboard. +5. Add environment variables per environment (see + [Environment Variables](#environment-variables)). Nothing sensitive is + committed to `vercel.json`. + +### Docs project (`@worksight/docs`) โ€” optional on Vercel + +1. Create a **separate** Vercel project from the same repo. +2. **Root Directory โ†’ `apps/docs`** so Vercel loads `apps/docs/vercel.json`. +3. Keep "Include source files outside of the Root Directory" enabled. + +Docs are otherwise published to GitHub Pages; the Vercel path is optional. + +### API project (`@worksight/api`) + +`apps/api/vercel.json` now uses zero-config build settings (`pnpm --filter` +build into `dist`) instead of the legacy `builds`/`routes` block, which silently +disabled Vercel's install and build steps. + +**This is still not a functioning serverless API.** `main.ts` calls +`app.listen()` rather than exporting a handler, so a Vercel deployment produces +no invocable function. Wiring it properly requires adding a serverless entry +(e.g. `api/index.ts` exporting the bootstrapped Nest app). Until then, deploy +the API with Docker: -### One-Click Deploy - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/4sightorg/worksight) - -### Manual Deployment - -1. **Install Vercel CLI** - - ```bash - pnpm add -g vercel - ``` - -2. **Login to Vercel** - - ```bash - vercel login - ``` - -3. **Deploy Preview** - - ```bash - pnpm run deploy:preview - ``` - -4. **Deploy Production** - - ```bash - pnpm run deploy - ``` +```bash +docker compose up -d --build +``` ## Environment Variables @@ -59,13 +115,19 @@ NEXT_PUBLIC_IS_OFFLINE="true" ## Vercel Configuration -The `vercel.json` file includes: +`apps/web/vercel.json` (loaded by the `worksight` project) declares: + +- `framework: nextjs` โ€” output directory left to the framework default +- `installCommand: pnpm install --frozen-lockfile` +- `buildCommand: pnpm --filter @worksight/web build` +- `NEXT_TELEMETRY_DISABLED=1` for the build step + +`apps/api/vercel.json` and `apps/docs/vercel.json` mirror the same shape with +their own filter and output directory. No environment values are committed โ€” +they are set per environment in the dashboard. -- **Optimized builds** with Next.js -- **Security headers** for production -- **API route configuration** -- **Redirects and rewrites** -- **CORS headers** for API endpoints +Security headers, redirects, and CORS are **not** currently configured in +`vercel.json`; add them here if/when needed rather than assuming they exist. ## Code Quality Checks diff --git a/docs/handoffs/2026-07-25-centralize-deployments.md b/docs/handoffs/2026-07-25-centralize-deployments.md index a7deca4..a5564e9 100644 --- a/docs/handoffs/2026-07-25-centralize-deployments.md +++ b/docs/handoffs/2026-07-25-centralize-deployments.md @@ -1,24 +1,61 @@ # HANDOFF โ€” Centralize deployments (#20) -**Status:** Planned -**Branch:** `feat/mvp-deploy` (after stabilize #15) +**Status:** In progress (repo-side config done) +**Branch:** `feat/vercel-monorepo-config-20` **Issue(s):** #20 **Last updated:** 2026-07-25 ## Bottom line -One primary deploy path for web + api + docs; root env matrix; CI path filters; demote split vercel.json drift. + +Three Vercel projects (`worksight`, `worksight-api`, `worksight-docs`), one +uniform per-app `vercel.json` each, all using the same pnpm workspace install +and filtered build. Dashboard settings (Root Directory, production branch +`canary`, skip-unaffected deploys) were applied via the Vercel API and are +documented here. + +## What changed (repo-side) + +- `apps/web/vercel.json`: `framework: nextjs`, `pnpm install --frozen-lockfile`, + `pnpm --filter @worksight/web build`, telemetry off. No `outputDirectory` + (Next.js preset default), and the previously committed `NEXT_PUBLIC_*` values + โ€” including the Supabase anon key โ€” removed in favor of dashboard env vars. +- `apps/docs/vercel.json`: same shape, output `.vitepress/dist`. +- `apps/api/vercel.json`: replaced the legacy `builds`/`routes` block (which + disabled Vercel's install/build steps entirely) with zero-config build + settings; output `dist`. +- `doc/DEPLOYMENT.md` + `README.md`: per-project table with actual Root + Directory values, dashboard steps, and an honest note that the API has no + serverless handler yet. + +## Dashboard state (applied via Vercel API) + +- Root Directory: `apps/web` / `apps/api` / `apps/docs` respectively. +- "Include source files outside of the Root Directory" enabled (workspace + `packages/*` + lockfile). +- Skip deploys for unaffected projects enabled. +- Production branch `canary`. +- Env vars remain dashboard-managed per environment. + +## Remaining + +- API needs a serverless entry (`main.ts` calls `app.listen()`), or stays on + Docker. +- API env matrix is Docker-side only. ## Current state -- `apps/web/vercel.json`, `apps/api/vercel.json` + +- `apps/web/vercel.json`, `apps/api/vercel.json`, `apps/docs/vercel.json` - Root `docker-compose.yml` + `nginx/` + `doc/DEPLOYMENT.md` ## Hook points + - `docker-compose.yml`, `nginx/` - Root `.github/workflows` - Root `.env.example` - `doc/DEPLOYMENT.md`, README ## How to verify + ```bash docker compose config pnpm --filter @worksight/web build @@ -26,6 +63,7 @@ pnpm --filter @worksight/api build ``` ## Done means -- [ ] One documented SoT deploy path -- [ ] Env matrix covers web+api -- [ ] DEPLOYMENT.md matches reality + +- [x] One documented deploy path per app (uniform per-app `vercel.json`) +- [x] DEPLOYMENT.md matches reality (per-app targets + dashboard steps) +- [ ] Env matrix covers web+api (web done in config; api env still Docker-side) From 02da96cfbc59b0922e22a46f78665d7b010b54fe Mon Sep 17 00:00:00 2001 From: kuyacarlo <106532351+kuyacarlo@users.noreply.github.com> Date: Sat, 25 Jul 2026 14:54:55 +0800 Subject: [PATCH 2/2] fix(deploy): build via Turbo so workspace deps resolve on Vercel pnpm --filter build runs only that package's script and does not build its workspace dependencies first, so nest build fails with TS2307 on @worksight/common in a clean Vercel checkout. Turbo's dependsOn: ["^build"] handles the ordering. Caught by the worksight-api preview deployment. Co-authored-by: Cursor --- README.md | 2 +- apps/api/vercel.json | 2 +- apps/docs/vercel.json | 2 +- apps/web/vercel.json | 2 +- doc/DEPLOYMENT.md | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9bf99b0..6cd8e9f 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ based on its dashboard **Root Directory** setting: `apps/docs/vercel.json`. All three share `pnpm install --frozen-lockfile`, a -`pnpm --filter @worksight/ build` command, production branch `canary`, and +`pnpm turbo run build --filter=@worksight/` command, production branch `canary`, and skip-unaffected-project deploys. No environment values are committed to `vercel.json`. diff --git a/apps/api/vercel.json b/apps/api/vercel.json index 18511c9..0a14dae 100644 --- a/apps/api/vercel.json +++ b/apps/api/vercel.json @@ -1,6 +1,6 @@ { "$schema": "https://openapi.vercel.sh/vercel.json", "installCommand": "pnpm install --frozen-lockfile", - "buildCommand": "pnpm --filter @worksight/api build", + "buildCommand": "pnpm turbo run build --filter=@worksight/api", "outputDirectory": "dist" } diff --git a/apps/docs/vercel.json b/apps/docs/vercel.json index b346ec9..3327b5e 100644 --- a/apps/docs/vercel.json +++ b/apps/docs/vercel.json @@ -2,6 +2,6 @@ "$schema": "https://openapi.vercel.sh/vercel.json", "framework": "vitepress", "installCommand": "pnpm install --frozen-lockfile", - "buildCommand": "pnpm --filter @worksight/docs build", + "buildCommand": "pnpm turbo run build --filter=@worksight/docs", "outputDirectory": ".vitepress/dist" } diff --git a/apps/web/vercel.json b/apps/web/vercel.json index 6ee9a19..b760636 100644 --- a/apps/web/vercel.json +++ b/apps/web/vercel.json @@ -2,7 +2,7 @@ "$schema": "https://openapi.vercel.sh/vercel.json", "framework": "nextjs", "installCommand": "pnpm install --frozen-lockfile", - "buildCommand": "pnpm --filter @worksight/web build", + "buildCommand": "pnpm turbo run build --filter=@worksight/web", "build": { "env": { "NEXT_TELEMETRY_DISABLED": "1" diff --git a/doc/DEPLOYMENT.md b/doc/DEPLOYMENT.md index e49c43f..039acd9 100644 --- a/doc/DEPLOYMENT.md +++ b/doc/DEPLOYMENT.md @@ -38,8 +38,8 @@ project's **Root Directory** (a dashboard setting). It does **not** merge a root Every app config uses the same primitives, so behavior is consistent: - Install: `pnpm install --frozen-lockfile` -- Build: `pnpm --filter @worksight/ build` (pnpm runs the workspace build - script; Turbo's `dependsOn: ["^build"]` graph builds workspace dependencies +- Build: `pnpm turbo run build --filter=@worksight/` (Turbo runs the build + script; its `dependsOn: ["^build"]` graph builds workspace dependencies such as `@worksight/common` and `@worksight/assets` first). - Output: framework default for Next.js; explicit for docs (`.vitepress/dist`) and api (`dist`). @@ -119,7 +119,7 @@ NEXT_PUBLIC_IS_OFFLINE="true" - `framework: nextjs` โ€” output directory left to the framework default - `installCommand: pnpm install --frozen-lockfile` -- `buildCommand: pnpm --filter @worksight/web build` +- `buildCommand: pnpm turbo run build --filter=@worksight/web` - `NEXT_TELEMETRY_DISABLED=1` for the build step `apps/api/vercel.json` and `apps/docs/vercel.json` mirror the same shape with