From 0fbdd7edc595b490b2d23c2fbfcfd0e23c1bf340 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 03:39:51 +0000 Subject: [PATCH 01/12] Initial plan From 8805506be16ebb80315f14ae457b49b5f901059e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 03:42:41 +0000 Subject: [PATCH 02/12] fix: configure Vercel to serve API routes as serverless functions instead of HTML - Add outputDirectory: "public" to vercel.json - Add functions block declaring api/index.js as serverless function - Fix API rewrite destination from /api to /api/index.js - Update CHANGELOG.md Agent-Logs-Url: https://github.com/objectstack-ai/spec/sessions/d0116abe-0010-46e2-a25d-08bc5236156b Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ apps/studio/vercel.json | 9 ++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e1d725cd..5ea5582ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed +- **Studio Vercel API routes returning HTML instead of JSON** — Updated + `apps/studio/vercel.json` to correctly configure Vercel serverless functions. + Added `outputDirectory: "public"` so Vercel serves static files from the + correct directory, declared `api/index.js` in the `functions` block so Vercel + recognises it as a serverless function, and changed the API rewrite destination + from `/api` to `/api/index.js` to route requests to the bundled handler. - **Client test aligned with removed `ai.chat` method** — Updated `@objectstack/client` test suite to match the current API surface where `ai.chat()` was removed in favour of the Vercel AI SDK `useChat()` hook. diff --git a/apps/studio/vercel.json b/apps/studio/vercel.json index 0c1e45980..bf118f409 100644 --- a/apps/studio/vercel.json +++ b/apps/studio/vercel.json @@ -3,6 +3,13 @@ "framework": null, "installCommand": "cd ../.. && pnpm install", "buildCommand": "bash scripts/build-vercel.sh", + "outputDirectory": "public", + "functions": { + "api/index.js": { + "memory": 1024, + "maxDuration": 60 + } + }, "build": { "env": { "VITE_RUNTIME_MODE": "server", @@ -18,7 +25,7 @@ } ], "rewrites": [ - { "source": "/api/(.*)", "destination": "/api" }, + { "source": "/api/(.*)", "destination": "/api/index.js" }, { "source": "/((?!api/).*)", "destination": "/index.html" } ] } From e95f3a94a98b26a81cb520976b01d17f7b30484a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 04:02:20 +0000 Subject: [PATCH 03/12] fix: add CORS middleware for Vercel temporary/preview domain deployments Add hono/cors middleware to the outer Hono app in server/index.ts so the serverless function returns proper Access-Control-Allow-Origin headers for cross-origin requests from Vercel preview/temporary domains. Allowed origins: - All Vercel deployment URLs from VERCEL_URL, VERCEL_BRANCH_URL, VERCEL_PROJECT_PRODUCTION_URL env vars - Any *.vercel.app subdomain (covers all preview deployments) - localhost for local development The CORS middleware is placed before the catch-all route so OPTIONS preflight requests are handled immediately without kernel boot latency. Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/324fd769-65e6-430a-aab8-06ad343e75f2 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 7 ++++++ apps/studio/server/index.ts | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ea5582ce..02060c407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 correct directory, declared `api/index.js` in the `functions` block so Vercel recognises it as a serverless function, and changed the API rewrite destination from `/api` to `/api/index.js` to route requests to the bundled handler. +- **Studio CORS error on Vercel temporary/preview domains** — Added Hono CORS + middleware to `apps/studio/server/index.ts` so the serverless function returns + correct `Access-Control-Allow-Origin` headers for cross-origin requests. + Dynamically allows all `*.vercel.app` subdomains, explicitly listed Vercel + deployment URLs (`VERCEL_URL`, `VERCEL_BRANCH_URL`, + `VERCEL_PROJECT_PRODUCTION_URL`), and localhost for development. Preflight + (OPTIONS) requests are answered immediately without waiting for kernel boot. - **Client test aligned with removed `ai.chat` method** — Updated `@objectstack/client` test suite to match the current API surface where `ai.chat()` was removed in favour of the Vercel AI SDK `useChat()` hook. diff --git a/apps/studio/server/index.ts b/apps/studio/server/index.ts index 6005d1950..97701b743 100644 --- a/apps/studio/server/index.ts +++ b/apps/studio/server/index.ts @@ -36,6 +36,7 @@ import { MetadataPlugin } from '@objectstack/metadata'; import { AIServicePlugin } from '@objectstack/service-ai'; import { handle } from '@hono/node-server/vercel'; import { Hono } from 'hono'; +import { cors } from 'hono/cors'; import { createBrokerShim } from '../src/lib/create-broker-shim.js'; import studioConfig from '../objectstack.config.js'; @@ -218,6 +219,50 @@ async function ensureApp(): Promise { */ const app = new Hono(); +// --------------------------------------------------------------------------- +// CORS middleware +// --------------------------------------------------------------------------- +// Placed on the outer app so preflight (OPTIONS) requests are answered +// immediately, without waiting for the kernel cold-start. This is essential +// when the SPA is loaded from a Vercel temporary/preview domain but the +// API base URL points to a different deployment (cross-origin). +// +// Allowed origins: +// 1. All Vercel deployment URLs exposed via env vars (current deployment) +// 2. Any *.vercel.app subdomain (covers all preview/branch deployments) +// 3. localhost (local development) +// --------------------------------------------------------------------------- + +const vercelOrigins: string[] = []; +if (process.env.VERCEL_URL) { + vercelOrigins.push(`https://${process.env.VERCEL_URL}`); +} +if (process.env.VERCEL_BRANCH_URL) { + vercelOrigins.push(`https://${process.env.VERCEL_BRANCH_URL}`); +} +if (process.env.VERCEL_PROJECT_PRODUCTION_URL) { + vercelOrigins.push(`https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`); +} + +app.use('*', cors({ + origin: (origin) => { + // Same-origin or non-browser requests (no Origin header) + if (!origin) return origin; + // Explicitly listed Vercel deployment origins + if (vercelOrigins.includes(origin)) return origin; + // Any *.vercel.app subdomain (preview / temp deployments) + if (origin.endsWith('.vercel.app') && origin.startsWith('https://')) return origin; + // Localhost for development + if (/^https?:\/\/localhost(:\d+)?$/.test(origin)) return origin; + // Deny — return empty string so no Access-Control-Allow-Origin is set + return ''; + }, + credentials: true, + allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], + maxAge: 86400, +})); + app.all('*', async (c) => { console.log(`[Vercel] ${c.req.method} ${c.req.url}`); From 399f60263f7db66358bcc9caae7f71594f067e4d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 05:17:45 +0000 Subject: [PATCH 04/12] fix: remove functions block from vercel.json to fix pre-build validation error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `functions` block pattern `api/index.js` fails Vercel's pre-build validation because the file is generated during the build step (by bundle-api.mjs), not available beforehand. The in-code `export const config` in server/index.ts already configures memory and maxDuration — Vercel's @vercel/node runtime picks it up from the deployed function bundle without any vercel.json pattern matching. Also reverts the rewrite destination to `/api` (the auto-detected route for api/index.js) instead of `/api/index.js` (a file path that doesn't map to a Vercel route). Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/16819ebf-4d21-4ae9-892d-8c1593b21504 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- CHANGELOG.md | 7 ++++--- apps/studio/vercel.json | 8 +------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02060c407..6d8c79cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Studio Vercel API routes returning HTML instead of JSON** — Updated `apps/studio/vercel.json` to correctly configure Vercel serverless functions. Added `outputDirectory: "public"` so Vercel serves static files from the - correct directory, declared `api/index.js` in the `functions` block so Vercel - recognises it as a serverless function, and changed the API rewrite destination - from `/api` to `/api/index.js` to route requests to the bundled handler. + correct directory and the `api/` directory is auto-detected for serverless + functions. Removed the `functions` block (the in-code `export const config` + in `server/index.ts` already configures memory/maxDuration without pre-build + file-pattern validation errors). - **Studio CORS error on Vercel temporary/preview domains** — Added Hono CORS middleware to `apps/studio/server/index.ts` so the serverless function returns correct `Access-Control-Allow-Origin` headers for cross-origin requests. diff --git a/apps/studio/vercel.json b/apps/studio/vercel.json index bf118f409..1bfd998fb 100644 --- a/apps/studio/vercel.json +++ b/apps/studio/vercel.json @@ -4,12 +4,6 @@ "installCommand": "cd ../.. && pnpm install", "buildCommand": "bash scripts/build-vercel.sh", "outputDirectory": "public", - "functions": { - "api/index.js": { - "memory": 1024, - "maxDuration": 60 - } - }, "build": { "env": { "VITE_RUNTIME_MODE": "server", @@ -25,7 +19,7 @@ } ], "rewrites": [ - { "source": "/api/(.*)", "destination": "/api/index.js" }, + { "source": "/api/(.*)", "destination": "/api" }, { "source": "/((?!api/).*)", "destination": "/index.html" } ] } From 76e66adc63fa18b0eda87b78285925ec233a0ef6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 05:52:40 +0000 Subject: [PATCH 05/12] fix: use same-origin API for all Vercel deployments and extract getVercelOrigins helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change VITE_SERVER_URL from hardcoded 'https://play.objectstack.ai' to '' (empty string / same-origin) so each Vercel deployment — including previews — calls its own serverless function instead of the production API cross-origin. Extract getVercelOrigins() helper to deduplicate Vercel env-var origin collection between the CORS middleware and better-auth trustedOrigins. Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/bffd519d-cf16-402d-a1de-3bdaadfdd5d2 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 16 +++++++----- apps/studio/server/index.ts | 51 ++++++++++++++++++++++--------------- apps/studio/vercel.json | 2 +- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d8c79cd8..5ef4ade95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,13 +15,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 functions. Removed the `functions` block (the in-code `export const config` in `server/index.ts` already configures memory/maxDuration without pre-build file-pattern validation errors). -- **Studio CORS error on Vercel temporary/preview domains** — Added Hono CORS - middleware to `apps/studio/server/index.ts` so the serverless function returns - correct `Access-Control-Allow-Origin` headers for cross-origin requests. - Dynamically allows all `*.vercel.app` subdomains, explicitly listed Vercel - deployment URLs (`VERCEL_URL`, `VERCEL_BRANCH_URL`, - `VERCEL_PROJECT_PRODUCTION_URL`), and localhost for development. Preflight - (OPTIONS) requests are answered immediately without waiting for kernel boot. +- **Studio CORS error on Vercel temporary/preview domains** — Changed + `VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""` + (empty string / same-origin) in `vercel.json` so each deployment — including + previews — calls its own serverless function instead of the production API + cross-origin. Also added Hono CORS middleware to `apps/studio/server/index.ts` + as a safety net for any remaining cross-origin scenarios; dynamically allows + all `*.vercel.app` subdomains, explicitly listed Vercel deployment URLs, and + localhost. Extracted `getVercelOrigins()` helper to keep CORS and + better-auth `trustedOrigins` allowlists in sync. - **Client test aligned with removed `ai.chat` method** — Updated `@objectstack/client` test suite to match the current API surface where `ai.chat()` was removed in favour of the Vercel AI SDK `useChat()` hook. diff --git a/apps/studio/server/index.ts b/apps/studio/server/index.ts index 97701b743..1c39aca8d 100644 --- a/apps/studio/server/index.ts +++ b/apps/studio/server/index.ts @@ -40,6 +40,33 @@ import { cors } from 'hono/cors'; import { createBrokerShim } from '../src/lib/create-broker-shim.js'; import studioConfig from '../objectstack.config.js'; +// --------------------------------------------------------------------------- +// Vercel origin helpers +// --------------------------------------------------------------------------- + +/** + * Collect all Vercel deployment origins from environment variables. + * + * Reused for both: + * - better-auth `trustedOrigins` (CSRF) + * - Hono CORS middleware `origin` allowlist + * + * Centralised to avoid drift between the two allowlists. + */ +function getVercelOrigins(): string[] { + const origins: string[] = []; + if (process.env.VERCEL_URL) { + origins.push(`https://${process.env.VERCEL_URL}`); + } + if (process.env.VERCEL_BRANCH_URL) { + origins.push(`https://${process.env.VERCEL_BRANCH_URL}`); + } + if (process.env.VERCEL_PROJECT_PRODUCTION_URL) { + origins.push(`https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`); + } + return origins; +} + // --------------------------------------------------------------------------- // Singleton state — persists across warm Vercel invocations // --------------------------------------------------------------------------- @@ -86,17 +113,8 @@ async function ensureKernel(): Promise { ? `https://${process.env.VERCEL_URL}` : 'http://localhost:3000'; - // Collect all Vercel URL variants so better-auth trusts each one - const trustedOrigins: string[] = []; - if (process.env.VERCEL_URL) { - trustedOrigins.push(`https://${process.env.VERCEL_URL}`); - } - if (process.env.VERCEL_BRANCH_URL) { - trustedOrigins.push(`https://${process.env.VERCEL_BRANCH_URL}`); - } - if (process.env.VERCEL_PROJECT_PRODUCTION_URL) { - trustedOrigins.push(`https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`); - } + // Reuse the shared helper so CORS and CSRF allowlists stay in sync + const trustedOrigins = getVercelOrigins(); await kernel.use(new AuthPlugin({ secret: process.env.AUTH_SECRET || 'dev-secret-please-change-in-production-min-32-chars', @@ -233,16 +251,7 @@ const app = new Hono(); // 3. localhost (local development) // --------------------------------------------------------------------------- -const vercelOrigins: string[] = []; -if (process.env.VERCEL_URL) { - vercelOrigins.push(`https://${process.env.VERCEL_URL}`); -} -if (process.env.VERCEL_BRANCH_URL) { - vercelOrigins.push(`https://${process.env.VERCEL_BRANCH_URL}`); -} -if (process.env.VERCEL_PROJECT_PRODUCTION_URL) { - vercelOrigins.push(`https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`); -} +const vercelOrigins = getVercelOrigins(); app.use('*', cors({ origin: (origin) => { diff --git a/apps/studio/vercel.json b/apps/studio/vercel.json index 1bfd998fb..d0b34189f 100644 --- a/apps/studio/vercel.json +++ b/apps/studio/vercel.json @@ -7,7 +7,7 @@ "build": { "env": { "VITE_RUNTIME_MODE": "server", - "VITE_SERVER_URL": "https://play.objectstack.ai" + "VITE_SERVER_URL": "" } }, "headers": [ From 15073a57c98db6cb624072488c8ff97f3a924ba2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 06:07:42 +0000 Subject: [PATCH 06/12] fix: copy API serverless function into output directory for Vercel detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vercel's `framework: null` defaults outputDirectory to `public/` when that directory exists and only detects serverless functions INSIDE the output directory. The bundled `api/index.js` was at the project root (outside `public/`) so Vercel never recognised it — all `/api/*` routes fell through to the SPA catch-all and returned `index.html`. Added step 4 to `build-vercel.sh` that copies `api/index.js` into `public/api/` after the SPA files are placed in `public/`. This ensures Vercel detects it as a serverless function within the output directory. Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/85727540-2aac-406e-83f5-790a0c650da3 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 14 +++++++++----- apps/studio/scripts/build-vercel.sh | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ef4ade95..f3f3c9e3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Studio Vercel API routes returning HTML instead of JSON** — Updated - `apps/studio/vercel.json` to correctly configure Vercel serverless functions. - Added `outputDirectory: "public"` so Vercel serves static files from the - correct directory and the `api/` directory is auto-detected for serverless - functions. Removed the `functions` block (the in-code `export const config` - in `server/index.ts` already configures memory/maxDuration without pre-build + `apps/studio/scripts/build-vercel.sh` to copy the bundled serverless function + (`api/index.js`) into the output directory (`public/api/`). Vercel's + `framework: null` defaults `outputDirectory` to `public/` when that directory + exists, and only detects serverless functions **inside** the output directory. + Previously `api/index.js` was generated at the project root (outside `public/`) + and Vercel never recognised it, causing all `/api/*` routes to fall through to + the SPA catch-all rewrite. Also set `outputDirectory: "public"` explicitly in + `vercel.json` for clarity, and removed the `functions` block (the in-code + `export const config` already configures memory/maxDuration without pre-build file-pattern validation errors). - **Studio CORS error on Vercel temporary/preview domains** — Changed `VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""` diff --git a/apps/studio/scripts/build-vercel.sh b/apps/studio/scripts/build-vercel.sh index 2c20e69b3..90244887d 100755 --- a/apps/studio/scripts/build-vercel.sh +++ b/apps/studio/scripts/build-vercel.sh @@ -3,13 +3,15 @@ set -euo pipefail # Build script for Vercel deployment of @objectstack/studio. # -# Without outputDirectory, Vercel serves static files from public/. -# Serverless functions are detected from api/ at the project root. +# Vercel uses outputDirectory (vercel.json → "public") for BOTH static files +# and serverless function detection. The api/ subdirectory MUST be inside +# the output directory — files at the project root are ignored. # # Steps: # 1. Turbo build (Vite SPA → dist/) # 2. Bundle the API serverless function (→ api/index.js) # 3. Copy Vite output to public/ for Vercel CDN serving +# 4. Copy API function into public/api/ so Vercel detects it echo "[build-vercel] Starting studio build..." @@ -26,4 +28,11 @@ rm -rf public mkdir -p public cp -r dist/* public/ -echo "[build-vercel] Done. Static files in public/, serverless function in api/index.js" +# 4. Copy API function into output directory for Vercel detection +# Vercel only looks for serverless functions inside outputDirectory. +# Without this step, api/index.js at the project root is invisible. +mkdir -p public/api +cp api/index.js public/api/ +cp api/index.js.map public/api/ 2>/dev/null || true + +echo "[build-vercel] Done. Static files + API function in public/" From a249322a218ddf3e20320526bfdfc56956c2d8d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 06:57:20 +0000 Subject: [PATCH 07/12] fix: follow hotcrm Vercel deployment pattern for API function detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adapts the studio's Vercel deployment to match the proven hotcrm pattern: 1. Created committed `api/[[...route]].ts` catch-all route so Vercel can detect it as a serverless function during pre-build phase. 2. Changed esbuild output format from CJS to ESM — the studio's package.json has "type": "module", which causes Node.js to parse .js files as ESM. The previous CJS output (require/module.exports) would fail to parse. 3. Changed esbuild output path from `api/index.js` to `api/[[...route]].js` matching the catch-all naming convention used by hotcrm. 4. Updated vercel.json rewrites from `/api/(.*)` → `/api` to `/api/:path*` → `/api/[[...route]]` matching the hotcrm pattern. 5. Removed `outputDirectory: "public"` — Vercel's default for framework:null already serves static from public/ and functions from api/ at project root. 6. Reverted build-vercel.sh to original (no copy-to-public/api step). Reference: https://github.com/objectstack-ai/hotcrm/blob/main/vercel.json Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/15dce836-8636-4011-9192-87275ec8c7ce Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 17 ++++++----------- apps/studio/.gitignore | 3 ++- apps/studio/api/[[...route]].ts | 21 +++++++++++++++++++++ apps/studio/scripts/build-vercel.sh | 23 ++++++++++------------- apps/studio/scripts/bundle-api.mjs | 10 +++++++--- apps/studio/server/index.ts | 2 +- apps/studio/vercel.json | 3 +-- 7 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 apps/studio/api/[[...route]].ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f3f3c9e3e..be8282608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,17 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed -- **Studio Vercel API routes returning HTML instead of JSON** — Updated - `apps/studio/scripts/build-vercel.sh` to copy the bundled serverless function - (`api/index.js`) into the output directory (`public/api/`). Vercel's - `framework: null` defaults `outputDirectory` to `public/` when that directory - exists, and only detects serverless functions **inside** the output directory. - Previously `api/index.js` was generated at the project root (outside `public/`) - and Vercel never recognised it, causing all `/api/*` routes to fall through to - the SPA catch-all rewrite. Also set `outputDirectory: "public"` explicitly in - `vercel.json` for clarity, and removed the `functions` block (the in-code - `export const config` already configures memory/maxDuration without pre-build - file-pattern validation errors). +- **Studio Vercel API routes returning HTML instead of JSON** — Adopted the + same Vercel deployment pattern used by `hotcrm`: committed + `api/[[...route]].ts` catch-all route so Vercel detects it pre-build, + switched esbuild output from CJS to ESM (fixes `"type": "module"` conflict), + and changed the output path from `api/index.js` to `api/[[...route]].js`. + Updated rewrites to match: `/api/:path*` → `/api/[[...route]]`. - **Studio CORS error on Vercel temporary/preview domains** — Changed `VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""` (empty string / same-origin) in `vercel.json` so each deployment — including diff --git a/apps/studio/.gitignore b/apps/studio/.gitignore index a6c2cc954..05e38de50 100644 --- a/apps/studio/.gitignore +++ b/apps/studio/.gitignore @@ -5,7 +5,8 @@ pnpm-lock.yaml # Build outputs dist build -api +api/*.js +api/*.js.map *.tsbuildinfo # Development diff --git a/apps/studio/api/[[...route]].ts b/apps/studio/api/[[...route]].ts new file mode 100644 index 000000000..df30f9dc1 --- /dev/null +++ b/apps/studio/api/[[...route]].ts @@ -0,0 +1,21 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Vercel Serverless Function — Catch-all API route. + * + * This file MUST be committed to the repository so Vercel can detect it + * as a serverless function during the pre-build phase. + * + * During the Vercel build step, `scripts/bundle-api.mjs` uses esbuild to + * bundle `server/index.ts` (with all workspace dependencies inlined) and + * outputs the result as `api/[[...route]].js`, which replaces this file + * at deploy time. The esbuild bundle is used instead of Vercel's native + * TypeScript compilation because pnpm strict mode (no shamefully-hoist) + * prevents @vercel/node from resolving workspace:* packages correctly. + * + * @see {@link ../server/index.ts} — the actual server entrypoint + * @see {@link ../scripts/bundle-api.mjs} — the esbuild bundler + * @see {@link https://github.com/objectstack-ai/hotcrm/blob/main/vercel.json} — reference deployment + */ + +export { default, config } from '../server/index'; diff --git a/apps/studio/scripts/build-vercel.sh b/apps/studio/scripts/build-vercel.sh index 90244887d..7bbe7e92e 100755 --- a/apps/studio/scripts/build-vercel.sh +++ b/apps/studio/scripts/build-vercel.sh @@ -3,15 +3,19 @@ set -euo pipefail # Build script for Vercel deployment of @objectstack/studio. # -# Vercel uses outputDirectory (vercel.json → "public") for BOTH static files -# and serverless function detection. The api/ subdirectory MUST be inside -# the output directory — files at the project root are ignored. +# Follows the same Vercel deployment pattern as hotcrm: +# - api/[[...route]].ts is committed to the repo (Vercel detects it pre-build) +# - esbuild bundles server/index.ts → api/[[...route]].js (replaces TS at deploy) +# - Vite SPA output is copied to public/ for CDN serving +# +# Vercel routing (framework: null, no outputDirectory): +# - Static files: served from public/ +# - Serverless functions: detected from api/ at project root # # Steps: # 1. Turbo build (Vite SPA → dist/) -# 2. Bundle the API serverless function (→ api/index.js) +# 2. Bundle the API serverless function (→ api/[[...route]].js) # 3. Copy Vite output to public/ for Vercel CDN serving -# 4. Copy API function into public/api/ so Vercel detects it echo "[build-vercel] Starting studio build..." @@ -28,11 +32,4 @@ rm -rf public mkdir -p public cp -r dist/* public/ -# 4. Copy API function into output directory for Vercel detection -# Vercel only looks for serverless functions inside outputDirectory. -# Without this step, api/index.js at the project root is invisible. -mkdir -p public/api -cp api/index.js public/api/ -cp api/index.js.map public/api/ 2>/dev/null || true - -echo "[build-vercel] Done. Static files + API function in public/" +echo "[build-vercel] Done. Static files in public/, serverless function in api/[[...route]].js" diff --git a/apps/studio/scripts/bundle-api.mjs b/apps/studio/scripts/bundle-api.mjs index 96781fa15..d2978e58b 100644 --- a/apps/studio/scripts/bundle-api.mjs +++ b/apps/studio/scripts/bundle-api.mjs @@ -35,13 +35,17 @@ await build({ entryPoints: ['server/index.ts'], bundle: true, platform: 'node', - format: 'cjs', + format: 'esm', target: 'es2020', - outfile: 'api/index.js', + outfile: 'api/[[...route]].js', sourcemap: true, external: EXTERNAL, // Silence warnings about optional/unused require() calls in knex drivers logOverride: { 'require-resolve-not-external': 'silent' }, + // Vercel resolves ESM .js files correctly when "type": "module" is set. + // CJS format would conflict with the project's "type": "module" setting, + // causing Node.js to fail parsing require()/module.exports as ESM syntax. + banner: { js: '// Bundled by esbuild — see scripts/bundle-api.mjs' }, }); -console.log('[bundle-api] Bundled server/index.ts → api/index.js'); +console.log('[bundle-api] Bundled server/index.ts → api/[[...route]].js'); diff --git a/apps/studio/server/index.ts b/apps/studio/server/index.ts index 1c39aca8d..a34eb9761 100644 --- a/apps/studio/server/index.ts +++ b/apps/studio/server/index.ts @@ -295,7 +295,7 @@ export default handle(app); /** * Vercel per-function configuration. * - * Picked up by the @vercel/node runtime from the deployed api/index.js bundle. + * Picked up by the @vercel/node runtime from the deployed api/[[...route]].js bundle. * Replaces the top-level "functions" key in vercel.json so there is no * pre-build file-pattern validation against a not-yet-bundled artifact. */ diff --git a/apps/studio/vercel.json b/apps/studio/vercel.json index d0b34189f..81c6048b3 100644 --- a/apps/studio/vercel.json +++ b/apps/studio/vercel.json @@ -3,7 +3,6 @@ "framework": null, "installCommand": "cd ../.. && pnpm install", "buildCommand": "bash scripts/build-vercel.sh", - "outputDirectory": "public", "build": { "env": { "VITE_RUNTIME_MODE": "server", @@ -19,7 +18,7 @@ } ], "rewrites": [ - { "source": "/api/(.*)", "destination": "/api" }, + { "source": "/api/:path*", "destination": "/api/[[...route]]" }, { "source": "/((?!api/).*)", "destination": "/index.html" } ] } From ab6ad6a95224e782fd0d5424e4f6c0bfb8bf1061 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 07:09:48 +0000 Subject: [PATCH 08/12] fix: remove TS stub after esbuild bundle to prevent Vercel ERR_MODULE_NOT_FOUND Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/0302d779-7522-4028-ae9c-50d58b9fa1e9 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 3 +++ apps/studio/scripts/build-vercel.sh | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be8282608..ac04ec933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 switched esbuild output from CJS to ESM (fixes `"type": "module"` conflict), and changed the output path from `api/index.js` to `api/[[...route]].js`. Updated rewrites to match: `/api/:path*` → `/api/[[...route]]`. + Added post-bundle cleanup to remove the `.ts` stub so Vercel's + `@vercel/node` builder uses the self-contained esbuild bundle instead of + compiling the stub (which would fail with `ERR_MODULE_NOT_FOUND`). - **Studio CORS error on Vercel temporary/preview domains** — Changed `VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""` (empty string / same-origin) in `vercel.json` so each deployment — including diff --git a/apps/studio/scripts/build-vercel.sh b/apps/studio/scripts/build-vercel.sh index 7bbe7e92e..f8284e21d 100755 --- a/apps/studio/scripts/build-vercel.sh +++ b/apps/studio/scripts/build-vercel.sh @@ -26,6 +26,11 @@ cd apps/studio # 2. Bundle API serverless function node scripts/bundle-api.mjs +# Remove the TS source file so Vercel's @vercel/node builder uses the +# pre-bundled JS directly. Without this, Vercel compiles the TS stub +# itself, producing a thin re-export that references ../server/index — +# a file that doesn't exist at runtime (ERR_MODULE_NOT_FOUND). +rm -f "api/[[...route]].ts" # 3. Copy Vite build output to public/ for static file serving rm -rf public From cfc22275a3a3e8eda01092502c2f9683a4bf98a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 07:21:52 +0000 Subject: [PATCH 09/12] fix: use committed .js wrapper + separate _handler.js bundle to avoid Vercel TS compilation and file-not-found errors Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/656a5b4f-383c-4318-a657-a952f4445583 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 10 +++++----- apps/studio/.gitignore | 4 ++-- apps/studio/api/[[...route]].js | 17 +++++++++++++++++ apps/studio/api/[[...route]].ts | 21 --------------------- apps/studio/scripts/build-vercel.sh | 14 +++++--------- apps/studio/scripts/bundle-api.mjs | 4 ++-- 6 files changed, 31 insertions(+), 39 deletions(-) create mode 100644 apps/studio/api/[[...route]].js delete mode 100644 apps/studio/api/[[...route]].ts diff --git a/CHANGELOG.md b/CHANGELOG.md index ac04ec933..a8f28d351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,13 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Studio Vercel API routes returning HTML instead of JSON** — Adopted the same Vercel deployment pattern used by `hotcrm`: committed - `api/[[...route]].ts` catch-all route so Vercel detects it pre-build, + `api/[[...route]].js` catch-all route so Vercel detects it pre-build, switched esbuild output from CJS to ESM (fixes `"type": "module"` conflict), - and changed the output path from `api/index.js` to `api/[[...route]].js`. + and changed the bundle output to `api/_handler.js` (a separate file that + the committed wrapper re-exports). This avoids both Vercel's TS + compilation overwriting the bundle (`ERR_MODULE_NOT_FOUND`) and the + "File not found" error from deleting source files during build. Updated rewrites to match: `/api/:path*` → `/api/[[...route]]`. - Added post-bundle cleanup to remove the `.ts` stub so Vercel's - `@vercel/node` builder uses the self-contained esbuild bundle instead of - compiling the stub (which would fail with `ERR_MODULE_NOT_FOUND`). - **Studio CORS error on Vercel temporary/preview domains** — Changed `VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""` (empty string / same-origin) in `vercel.json` so each deployment — including diff --git a/apps/studio/.gitignore b/apps/studio/.gitignore index 05e38de50..4c09dba96 100644 --- a/apps/studio/.gitignore +++ b/apps/studio/.gitignore @@ -5,8 +5,8 @@ pnpm-lock.yaml # Build outputs dist build -api/*.js -api/*.js.map +api/_handler.js +api/_handler.js.map *.tsbuildinfo # Development diff --git a/apps/studio/api/[[...route]].js b/apps/studio/api/[[...route]].js new file mode 100644 index 000000000..864c37217 --- /dev/null +++ b/apps/studio/api/[[...route]].js @@ -0,0 +1,17 @@ +// Vercel Serverless Function — Catch-all API route. +// +// This file MUST be committed to the repository so Vercel can detect it +// as a serverless function during the pre-build phase. +// +// It delegates to the esbuild bundle (`_handler.js`) generated by +// `scripts/bundle-api.mjs` during the Vercel build step. A separate +// bundle file is used (rather than overwriting this file) so that: +// 1. Vercel always finds this committed entry point (no "File not found"). +// 2. Vercel does not TypeScript-compile a .ts stub that references +// source files absent at runtime (no ERR_MODULE_NOT_FOUND). +// +// @see ../server/index.ts — the actual server entrypoint +// @see ../scripts/bundle-api.mjs — the esbuild bundler +// @see https://github.com/objectstack-ai/hotcrm/blob/main/vercel.json + +export { default, config } from './_handler.js'; diff --git a/apps/studio/api/[[...route]].ts b/apps/studio/api/[[...route]].ts deleted file mode 100644 index df30f9dc1..000000000 --- a/apps/studio/api/[[...route]].ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * Vercel Serverless Function — Catch-all API route. - * - * This file MUST be committed to the repository so Vercel can detect it - * as a serverless function during the pre-build phase. - * - * During the Vercel build step, `scripts/bundle-api.mjs` uses esbuild to - * bundle `server/index.ts` (with all workspace dependencies inlined) and - * outputs the result as `api/[[...route]].js`, which replaces this file - * at deploy time. The esbuild bundle is used instead of Vercel's native - * TypeScript compilation because pnpm strict mode (no shamefully-hoist) - * prevents @vercel/node from resolving workspace:* packages correctly. - * - * @see {@link ../server/index.ts} — the actual server entrypoint - * @see {@link ../scripts/bundle-api.mjs} — the esbuild bundler - * @see {@link https://github.com/objectstack-ai/hotcrm/blob/main/vercel.json} — reference deployment - */ - -export { default, config } from '../server/index'; diff --git a/apps/studio/scripts/build-vercel.sh b/apps/studio/scripts/build-vercel.sh index f8284e21d..d06a8efa0 100755 --- a/apps/studio/scripts/build-vercel.sh +++ b/apps/studio/scripts/build-vercel.sh @@ -4,8 +4,9 @@ set -euo pipefail # Build script for Vercel deployment of @objectstack/studio. # # Follows the same Vercel deployment pattern as hotcrm: -# - api/[[...route]].ts is committed to the repo (Vercel detects it pre-build) -# - esbuild bundles server/index.ts → api/[[...route]].js (replaces TS at deploy) +# - api/[[...route]].js is committed to the repo (Vercel detects it pre-build) +# - esbuild bundles server/index.ts → api/_handler.js (self-contained bundle) +# - The committed .js wrapper re-exports from _handler.js at runtime # - Vite SPA output is copied to public/ for CDN serving # # Vercel routing (framework: null, no outputDirectory): @@ -14,7 +15,7 @@ set -euo pipefail # # Steps: # 1. Turbo build (Vite SPA → dist/) -# 2. Bundle the API serverless function (→ api/[[...route]].js) +# 2. Bundle the API serverless function (→ api/_handler.js) # 3. Copy Vite output to public/ for Vercel CDN serving echo "[build-vercel] Starting studio build..." @@ -26,15 +27,10 @@ cd apps/studio # 2. Bundle API serverless function node scripts/bundle-api.mjs -# Remove the TS source file so Vercel's @vercel/node builder uses the -# pre-bundled JS directly. Without this, Vercel compiles the TS stub -# itself, producing a thin re-export that references ../server/index — -# a file that doesn't exist at runtime (ERR_MODULE_NOT_FOUND). -rm -f "api/[[...route]].ts" # 3. Copy Vite build output to public/ for static file serving rm -rf public mkdir -p public cp -r dist/* public/ -echo "[build-vercel] Done. Static files in public/, serverless function in api/[[...route]].js" +echo "[build-vercel] Done. Static files in public/, serverless function in api/[[...route]].js → api/_handler.js" diff --git a/apps/studio/scripts/bundle-api.mjs b/apps/studio/scripts/bundle-api.mjs index d2978e58b..5c1280cff 100644 --- a/apps/studio/scripts/bundle-api.mjs +++ b/apps/studio/scripts/bundle-api.mjs @@ -37,7 +37,7 @@ await build({ platform: 'node', format: 'esm', target: 'es2020', - outfile: 'api/[[...route]].js', + outfile: 'api/_handler.js', sourcemap: true, external: EXTERNAL, // Silence warnings about optional/unused require() calls in knex drivers @@ -48,4 +48,4 @@ await build({ banner: { js: '// Bundled by esbuild — see scripts/bundle-api.mjs' }, }); -console.log('[bundle-api] Bundled server/index.ts → api/[[...route]].js'); +console.log('[bundle-api] Bundled server/index.ts → api/_handler.js'); From 6886a92cd5d03c4eb2df033ed199b05f754b5cc9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 07:40:30 +0000 Subject: [PATCH 10/12] fix: add createRequire banner to esbuild ESM bundle for Node.js built-in require() support Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/3888d7e6-570c-478b-b969-84043c288a94 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 3 +++ apps/studio/scripts/bundle-api.mjs | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8f28d351..341d731ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the committed wrapper re-exports). This avoids both Vercel's TS compilation overwriting the bundle (`ERR_MODULE_NOT_FOUND`) and the "File not found" error from deleting source files during build. + Added `createRequire` banner to the esbuild config so that CJS + dependencies (knex/tarn) can `require()` Node.js built-in modules like + `events` without the "Dynamic require is not supported" error. Updated rewrites to match: `/api/:path*` → `/api/[[...route]]`. - **Studio CORS error on Vercel temporary/preview domains** — Changed `VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""` diff --git a/apps/studio/scripts/bundle-api.mjs b/apps/studio/scripts/bundle-api.mjs index 5c1280cff..ec985e9d0 100644 --- a/apps/studio/scripts/bundle-api.mjs +++ b/apps/studio/scripts/bundle-api.mjs @@ -45,7 +45,19 @@ await build({ // Vercel resolves ESM .js files correctly when "type": "module" is set. // CJS format would conflict with the project's "type": "module" setting, // causing Node.js to fail parsing require()/module.exports as ESM syntax. - banner: { js: '// Bundled by esbuild — see scripts/bundle-api.mjs' }, + // + // The createRequire banner provides a real `require` function in the ESM + // scope. esbuild's __require shim (generated for CJS→ESM conversion) + // checks `typeof require !== "undefined"` and uses it when available, + // which fixes "Dynamic require of is not supported" errors + // from CJS dependencies like knex/tarn that require() Node.js built-ins. + banner: { + js: [ + '// Bundled by esbuild — see scripts/bundle-api.mjs', + 'import { createRequire } from "module";', + 'const require = createRequire(import.meta.url);', + ].join('\n'), + }, }); console.log('[bundle-api] Bundled server/index.ts → api/_handler.js'); From 1bc22cd525e0c3c6bd982c1898a8cc2d391fd1da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 07:49:32 +0000 Subject: [PATCH 11/12] fix: add functions.includeFiles for native addons (better-sqlite3, @libsql/client) on Vercel Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/fbeefcaa-1bf8-418e-aed0-baafcf1b18aa Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 2 ++ apps/studio/vercel.json | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 341d731ad..32c196900 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Added `createRequire` banner to the esbuild config so that CJS dependencies (knex/tarn) can `require()` Node.js built-in modules like `events` without the "Dynamic require is not supported" error. + Added `functions.includeFiles` in `vercel.json` to include native addons + (`better-sqlite3`, `@libsql/client`) that esbuild cannot bundle. Updated rewrites to match: `/api/:path*` → `/api/[[...route]]`. - **Studio CORS error on Vercel temporary/preview domains** — Changed `VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""` diff --git a/apps/studio/vercel.json b/apps/studio/vercel.json index 81c6048b3..ceb05c1f2 100644 --- a/apps/studio/vercel.json +++ b/apps/studio/vercel.json @@ -9,6 +9,13 @@ "VITE_SERVER_URL": "" } }, + "functions": { + "api/**/*.js": { + "memory": 1024, + "maxDuration": 60, + "includeFiles": "{node_modules/@libsql,node_modules/better-sqlite3}/**" + } + }, "headers": [ { "source": "/assets/(.*)", From 0fa5419ede6d17ab568808f258fd0007b2978ae1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 08:08:48 +0000 Subject: [PATCH 12/12] fix: copy native modules from monorepo root node_modules for pnpm strict mode on Vercel Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/5da7c570-c9a4-4a8d-93d4-bcb11dad0da9 Co-authored-by: xuyushun441-sys <255036401+xuyushun441-sys@users.noreply.github.com> --- CHANGELOG.md | 4 ++++ apps/studio/scripts/build-vercel.sh | 36 ++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32c196900..bf64f6913 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 `events` without the "Dynamic require is not supported" error. Added `functions.includeFiles` in `vercel.json` to include native addons (`better-sqlite3`, `@libsql/client`) that esbuild cannot bundle. + Added a build step to copy native external modules from the monorepo root + `node_modules/` into the studio's local `node_modules/`, since pnpm's strict + mode (unlike hotcrm's `shamefully-hoist`) doesn't symlink transitive native + dependencies into app-level `node_modules/`. Updated rewrites to match: `/api/:path*` → `/api/[[...route]]`. - **Studio CORS error on Vercel temporary/preview domains** — Changed `VITE_SERVER_URL` from hardcoded `https://play.objectstack.ai` to `""` diff --git a/apps/studio/scripts/build-vercel.sh b/apps/studio/scripts/build-vercel.sh index d06a8efa0..bd8908d79 100755 --- a/apps/studio/scripts/build-vercel.sh +++ b/apps/studio/scripts/build-vercel.sh @@ -28,7 +28,41 @@ cd apps/studio # 2. Bundle API serverless function node scripts/bundle-api.mjs -# 3. Copy Vite build output to public/ for static file serving +# 3. Copy native/external modules into local node_modules for Vercel packaging. +# +# Unlike hotcrm (which uses shamefully-hoist=true), this monorepo uses pnpm's +# default strict node_modules structure. Transitive native dependencies like +# better-sqlite3 only exist in the monorepo root's node_modules/.pnpm/ virtual +# store — they're NOT symlinked into apps/studio/node_modules/. +# +# The vercel.json includeFiles pattern references node_modules/ relative to +# apps/studio/, so we must copy the actual module files here for Vercel to +# include them in the serverless function's deployment package. +echo "[build-vercel] Copying external native modules to local node_modules..." +for mod in better-sqlite3; do + src="../../node_modules/$mod" + if [ -e "$src" ]; then + dest="node_modules/$mod" + mkdir -p "$(dirname "$dest")" + cp -rL "$src" "$dest" + echo "[build-vercel] ✓ Copied $mod" + else + echo "[build-vercel] ⚠ $mod not found at $src (skipped)" + fi +done +# Copy the @libsql scope (client + its sub-dependencies like core, hrana-client) +if [ -d "../../node_modules/@libsql" ]; then + mkdir -p "node_modules/@libsql" + for pkg in ../../node_modules/@libsql/*/; do + pkgname="$(basename "$pkg")" + cp -rL "$pkg" "node_modules/@libsql/$pkgname" + done + echo "[build-vercel] ✓ Copied @libsql/*" +else + echo "[build-vercel] ⚠ @libsql not found (skipped)" +fi + +# 4. Copy Vite build output to public/ for static file serving rm -rf public mkdir -p public cp -r dist/* public/