diff --git a/apps/web/app/future/apps/categories/[category]/page.tsx b/apps/web/app/future/apps/categories/[category]/page.tsx index 9105acc89b..cae4ba87a7 100644 --- a/apps/web/app/future/apps/categories/[category]/page.tsx +++ b/apps/web/app/future/apps/categories/[category]/page.tsx @@ -1,12 +1,11 @@ import CategoryPage, { type PageProps } from "@pages/apps/categories/[category]"; -import { Prisma } from "@prisma/client"; import { withAppDirSsg } from "app/WithAppDirSsg"; import { _generateMetadata } from "app/_utils"; import { WithLayout } from "app/layoutHOC"; import { APP_NAME } from "@calcom/lib/constants"; -import prisma from "@calcom/prisma"; import { AppCategories } from "@calcom/prisma/enums"; +import { isPrismaAvailableCheck } from "@calcom/prisma/is-prisma-available-check"; import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps"; @@ -19,16 +18,11 @@ export const generateMetadata = async () => { export const generateStaticParams = async () => { const paths = Object.keys(AppCategories); + const isPrismaAvailable = await isPrismaAvailableCheck(); - try { - await prisma.$queryRaw`SELECT 1`; - } catch (e: unknown) { - if (e instanceof Prisma.PrismaClientInitializationError) { - // Database is not available at build time. Make sure we fall back to building these pages on demand - return []; - } else { - throw e; - } + if (!isPrismaAvailable) { + // Database is not available at build time. Make sure we fall back to building these pages on demand + return []; } return paths.map((category) => ({ category })); diff --git a/apps/web/pages/apps/categories/[category].tsx b/apps/web/pages/apps/categories/[category].tsx index 86638d4572..36b49a2bcd 100644 --- a/apps/web/pages/apps/categories/[category].tsx +++ b/apps/web/pages/apps/categories/[category].tsx @@ -1,14 +1,13 @@ "use client"; -import { Prisma } from "@prisma/client"; import type { InferGetStaticPropsType } from "next"; import Link from "next/link"; import Shell from "@calcom/features/shell/Shell"; import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import prisma from "@calcom/prisma"; import { AppCategories } from "@calcom/prisma/enums"; +import { isPrismaAvailableCheck } from "@calcom/prisma/is-prisma-available-check"; import { AppCard, SkeletonText } from "@calcom/ui"; import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps"; @@ -62,19 +61,13 @@ Apps.PageWrapper = PageWrapper; export const getStaticPaths = async () => { const paths = Object.keys(AppCategories); - - try { - await prisma.$queryRaw`SELECT 1`; - } catch (e: unknown) { - if (e instanceof Prisma.PrismaClientInitializationError) { - // Database is not available at build time. Make sure we fall back to building these pages on demand - return { - paths: [], - fallback: "blocking", - }; - } else { - throw e; - } + const isPrismaAvailable = await isPrismaAvailableCheck(); + if (!isPrismaAvailable) { + // Database is not available at build time. Make sure we fall back to building these pages on demand + return { + paths: [], + fallback: "blocking", + }; } return { diff --git a/packages/prisma/auto-migrations.ts b/packages/prisma/auto-migrations.ts new file mode 100644 index 0000000000..3ae7c24a5f --- /dev/null +++ b/packages/prisma/auto-migrations.ts @@ -0,0 +1,42 @@ +import dotEnv from "dotenv"; +import { exec as execCb } from "node:child_process"; +import { promisify } from "node:util"; + +import { isPrismaAvailableCheck } from "./is-prisma-available-check"; + +dotEnv.config({ path: "../../.env" }); + +const exec = promisify(execCb); + +/** + * TODO: re-write this when Prisma.io gets a programmatic migration API + * Thanks to @olalonde for the idea. + * @see https://github.com/prisma/prisma/issues/4703#issuecomment-1447354363 + */ +async function main(): Promise { + if (!process.env.DATABASE_URL) { + console.info("No DATABASE_URL found, skipping migrations"); + return; + } + if (!process.env.DATABASE_DIRECT_URL) { + console.info("No DATABASE_DIRECT_URL found, skipping migrations"); + return; + } + if (!(await isPrismaAvailableCheck())) { + console.info("Prisma can't be initialized, skipping migrations"); + return; + } + // throws an error if migration fails + const { stdout, stderr } = await exec("yarn prisma migrate deploy", { + env: { + ...process.env, + }, + }); + console.log(stdout); + console.error(stderr); +} + +main().catch((e) => { + console.error(e.stdout || e.stderr || e.message); + process.exit(1); +}); diff --git a/packages/prisma/is-prisma-available-check.ts b/packages/prisma/is-prisma-available-check.ts new file mode 100644 index 0000000000..bdcf2e392c --- /dev/null +++ b/packages/prisma/is-prisma-available-check.ts @@ -0,0 +1,17 @@ +import { Prisma } from "@prisma/client"; + +import prisma from "."; + +export async function isPrismaAvailableCheck() { + try { + await prisma.$queryRaw`SELECT 1`; + return true; + } catch (e: unknown) { + if (e instanceof Prisma.PrismaClientInitializationError) { + // Database might not available at build time. + return false; + } else { + throw e; + } + } +} diff --git a/packages/prisma/package.json b/packages/prisma/package.json index a4cdc999b6..03a68935eb 100644 --- a/packages/prisma/package.json +++ b/packages/prisma/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "clean": "rm -rf .turbo && rm -rf node_modules", - "build": "yarn prisma migrate deploy || true", + "build": "ts-node --transpile-only ./auto-migrations.ts", "db-deploy": "yarn prisma migrate deploy", "db-migrate": "yarn prisma migrate dev", "db-nuke": "docker compose down --volumes --remove-orphans || docker-compose down --volumes --remove-orphans", diff --git a/turbo.json b/turbo.json index 8794e424b1..686658d1a6 100644 --- a/turbo.json +++ b/turbo.json @@ -237,6 +237,8 @@ "DAILY_API_KEY", "DAILY_SCALE_PLAN", "DAILY_WEBHOOK_SECRET", + "DATABASE_DIRECT_URL", + "DATABASE_URL", "DEBUG", "E2E_TEST_APPLE_CALENDAR_EMAIL", "E2E_TEST_APPLE_CALENDAR_PASSWORD",