Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/roam/build.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/bin/bash
set -e
export ROAM_BUILD_SCRIPT=1
npm install -g corepack@latest
corepack enable pnpm
pnpm install
Expand Down
22 changes: 22 additions & 0 deletions apps/website/app/api/supabase/env/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextResponse, NextRequest } from "next/server";
import {
handleRouteError,
defaultOptionsHandler,
} from "~/utils/supabase/apiUtils";

export const GET = (request: NextRequest): NextResponse => {
try {
const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env;
if (!SUPABASE_URL || !SUPABASE_ANON_KEY)
return new NextResponse("Missing variables", { status: 500 });
return NextResponse.json(
// eslint-disable-next-line @typescript-eslint/naming-convention
{ SUPABASE_URL, SUPABASE_ANON_KEY },
{ status: 200 },
);
Comment on lines +12 to +16

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Missing CORS headers on GET response while OPTIONS handler has them

The new /api/supabase/env endpoint has inconsistent CORS handling. The OPTIONS handler uses defaultOptionsHandler which applies CORS headers, but the GET handler returns NextResponse.json() directly without applying CORS via the cors() function.

Root Cause

Other API endpoints in the codebase use createApiResponse() which internally calls cors(request, response) to add CORS headers (see apps/website/app/utils/supabase/apiUtils.ts:48). However, this new endpoint bypasses that pattern.

If this endpoint is ever called from a browser context (e.g., from the Roam extension at runtime), the browser would:

  1. Send OPTIONS preflight → receives CORS headers (allowed)
  2. Send actual GET request → receives response WITHOUT CORS headers
  3. Browser rejects the response due to missing Access-Control-Allow-Origin

Currently this doesn't affect the build process since curl is used (packages/database/scripts/createEnv.mts:113), but it makes the API inconsistent and would break any future browser-based usage.

Suggested change
return NextResponse.json(
// eslint-disable-next-line @typescript-eslint/naming-convention
{ SUPABASE_URL, SUPABASE_ANON_KEY },
{ status: 200 },
);
const response = NextResponse.json(
// eslint-disable-next-line @typescript-eslint/naming-convention
{ SUPABASE_URL, SUPABASE_ANON_KEY },
{ status: 200 },
);
return cors(request, response) as NextResponse;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

} catch (e: unknown) {
return handleRouteError(request, e, "/api/supabase/env");
}
};

export const OPTIONS = defaultOptionsHandler;
21 changes: 19 additions & 2 deletions packages/database/scripts/createEnv.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
import dotenv from "dotenv";
import { Vercel } from "@vercel/sdk";

// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = dirname(fileURLToPath(import.meta.url));
const projectRoot = join(__dirname, "..");
const baseParams: Record<string, string> = {};
Expand Down Expand Up @@ -106,11 +107,27 @@ const makeProductionEnv = async (vercel: Vercel, vercelToken: string) => {
};

const main = async (variant: Variant) => {
// Do not execute in deployment or github action.
if (
if (process.env.ROAM_BUILD_SCRIPT) {
// special case: production build
try {
const response = execSync('curl https://discoursegraphs.com/api/supabase/env');
const asJson = JSON.parse(response.toString()) as Record<string, string>;
writeFileSync(
join(projectRoot, ".env"),
Object.entries(asJson).map(([k,v])=>`${k}=${v}`).join('\n')
);
Comment on lines +115 to +118

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Environment file written to wrong path - .env instead of .env.${variant} format

When ROAM_BUILD_SCRIPT=1 is set, createEnv.mts writes environment variables to packages/database/.env, but dbDotEnv.mjs:envFilePath() only reads files matching .env.${variant} pattern (like .env.local, .env.branch, .env.production).

Root Cause

The flow when ROAM_BUILD_SCRIPT=1:

  1. createEnv.mts:115-118 writes to join(projectRoot, ".env") - a plain .env file
  2. Later, compile.ts:153 calls envContents() from dbDotEnv.mjs
  3. dbDotEnv.mjs:64-68 in envFilePath() looks for .env.${variant} files only:
    const name = join(findRoot(), `.env.${variant}`);
    return existsSync(name) ? name : null;
  4. Since there's no .env.${variant} file and process.env doesn't have the variables, envContents() returns an empty object

Impact: The Roam build will not receive the SUPABASE_URL and SUPABASE_ANON_KEY values, causing the database connection to fail in the built extension. The fix should either write to .env.production (matching an existing variant) or add logic to read plain .env files.

Suggested change
writeFileSync(
join(projectRoot, ".env"),
Object.entries(asJson).map(([k,v])=>`${k}=${v}`).join('\n')
);
writeFileSync(
join(projectRoot, ".env.production"),
Object.entries(asJson).map(([k,v])=>`${k}=${v}`).join('\n')
);
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the base dotenv reads the content of .env in all cases.

return;
} catch (e) {
if (process.env.SUPABASE_URL && process.env.SUPABASE_ANON_KEY)
return;
throw new Error("Could not get environment from site");
}
}
else if (
process.env.HOME === "/vercel" ||
process.env.GITHUB_ACTIONS !== undefined
)
// Do not execute in deployment or github action.
return;

if (variant === Variant.none) return;
Expand Down
4 changes: 3 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"GEMINI_API_KEY",
"GH_CLIENT_SECRET_PROD",
"GITHUB_ACTIONS",
"HOME",
"OPENAI_API_KEY",
"POSTGRES_PASSWORD",
"POSTGRES_USER",
Expand All @@ -42,6 +43,7 @@
],
"tasks": {
"build": {
"env": ["ROAM_BUILD_SCRIPT"],
"dependsOn": ["@repo/database#genenv"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": [".next/**", "!.next/cache/**", "dist/**", "src/dbTypes.ts"]
Expand Down Expand Up @@ -69,7 +71,7 @@
"with": ["@repo/database#dev"]
},
"genenv": {
"env": ["SUPABASE_USE_DB"],
"env": ["SUPABASE_USE_DB", "ROAM_BUILD_SCRIPT"],
"cache": false,
"outputs": [".env*"]
},
Expand Down