diff --git a/apps/site/package.json b/apps/site/package.json
index f89768ff49..e53d838c7c 100644
--- a/apps/site/package.json
+++ b/apps/site/package.json
@@ -13,17 +13,20 @@
},
"dependencies": {
"@base-ui/react": "catalog:",
- "@prisma/eclipse": "workspace:^",
"@prisma-docs/ui": "workspace:*",
+ "@prisma/eclipse": "workspace:^",
+ "clsx": "catalog:",
"cors": "^2.8.6",
"lucide-react": "catalog:",
"next": "catalog:",
"npm-to-yarn": "catalog:",
+ "posthog-js": "catalog:",
"react": "catalog:",
"react-dom": "catalog:",
"react-tweet": "catalog:",
- "posthog-js": "catalog:",
"remark-directive": "catalog:",
+ "shiki": "3.22.0",
+ "tailwind-merge": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
@@ -32,11 +35,11 @@
"@types/node": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
+ "babel-plugin-react-compiler": "catalog:",
"next-validate-link": "catalog:",
"postcss": "catalog:",
"tailwindcss": "catalog:",
"tsx": "catalog:",
- "typescript": "catalog:",
- "babel-plugin-react-compiler": "catalog:"
+ "typescript": "catalog:"
}
}
diff --git a/apps/site/src/app/global.css b/apps/site/src/app/global.css
index f0100f4a23..654af4cc92 100644
--- a/apps/site/src/app/global.css
+++ b/apps/site/src/app/global.css
@@ -39,51 +39,56 @@
.newsletter-bg {
background: linear-gradient(
- 59deg,
- color-mix(in srgb, var(--color-foreground-ppg) 30%, transparent) 6.53%,
+ 59deg,
+ color-mix(in srgb, var(--color-foreground-ppg) 30%, transparent) 6.53%,
var(--color-background-default) 74.71%
- )
+ );
}
@keyframes glitch-1 {
0% {
- clip-path: inset(20% 0 60% 0);
+ clip-path: inset(20% 0 60% 0);
}
20% {
- clip-path: inset(10% 0 85% 0);
+ clip-path: inset(10% 0 85% 0);
}
40% {
- clip-path: inset(40% 0 40% 0);
+ clip-path: inset(40% 0 40% 0);
}
60% {
- clip-path: inset(80% 0 5% 0);
+ clip-path: inset(80% 0 5% 0);
}
80% {
- clip-path: inset(50% 0 30% 0);
+ clip-path: inset(50% 0 30% 0);
}
100% {
- clip-path: inset(25% 0 55% 0);
+ clip-path: inset(25% 0 55% 0);
}
- }
+}
@keyframes glitch-2 {
0% {
- clip-path: inset(80% 0 5% 0);
+ clip-path: inset(80% 0 5% 0);
}
20% {
- clip-path: inset(50% 0 30% 0);
+ clip-path: inset(50% 0 30% 0);
}
40% {
- clip-path: inset(20% 0 60% 0);
+ clip-path: inset(20% 0 60% 0);
}
60% {
- clip-path: inset(10% 0 85% 0);
+ clip-path: inset(10% 0 85% 0);
}
80% {
- clip-path: inset(40% 0 40% 0);
+ clip-path: inset(40% 0 40% 0);
}
100% {
- clip-path: inset(75% 0 15% 0);
+ clip-path: inset(75% 0 15% 0);
}
}
+.diff-add {
+ background: var(--color-background-orm);
+ width: 100%;
+ display: inline-block;
+}
diff --git a/apps/site/src/app/layout.tsx b/apps/site/src/app/layout.tsx
index 3fac2aaf3e..5fe704f063 100644
--- a/apps/site/src/app/layout.tsx
+++ b/apps/site/src/app/layout.tsx
@@ -121,11 +121,7 @@ function baseOptions() {
export default function Layout({ children }: { children: React.ReactNode }) {
return (
-
+
-
+
+
+
+
+
+ Prisma Migrate
+
+
+ Hassle-free
+
+ Database Migrations
+
+
+
+ Prisma Migrate uses Prisma schema changes to automatically generate
+ fully customizable database schema migrations
+
+
+
+ Install Prisma
+
+ {/*
+ Playground
+ */}
+
+
+
+
+
+
+
+
+
+
+
+
+ Auto-generated
+
+
+
+ Migrations are automatically generated so you don't have to
+ write the SQL by hand.
+
+
+
+
+
+
+
+
+ Deterministic / Repeatable
+
+
+
+ Migrate generates SQL migrations, ensuring migrations will
+ always result in the same database schema across environments.
+
+
+
+
+
+
+
+
+ Customizable
+
+
+
+ Generated SQL migrations can be fully customized giving you full
+ control over the exact changes.
+
+
+
+
+
+
+
+
+
+
+ Iteration
+
+
+ Fast in development
+
+
+
+
+
+
+ Prototype fast without migrations
+
+
+ While prototyping you can create the database schema
+ quickly using the prisma db push command without creating
+ migrations.
+
+
+
+
+
+
+
+
+ Integrated seeding
+
+
+ Quickly seed your database with data by defining a seed
+ script in JavaScript, TypeScript or Shell.
+
+
+
+
+
+
+
+ Smart problem resolution
+
+
+ Migrate detects database schema drift and assists you in
+ resolving them.
+
+
+
+
+
+
+
+ Deployment
+
+
+ Reliable in Production
+
+
+
+
+
+
+ Dedicated production workflows
+
+
+ Migrate supports dedicated workflows for carrying out
+ migrations safely in production.
+
+
+
+
+
+
+
+ CI/CD Integration
+
+
+ Migrate can be integrated into CI/CD pipelines, e.g.
+ GitHub Actions, to automate applying migrations before
+ deployment.
+
+
+
+
+
+
+
+ Conflict detection and resolution
+
+
+ Migrate keeps track of applied migrations and provides
+ tools to detect and resolve conflicts and drifts between
+ migrations and the database schema.
+
+
+
+
+
+
+
+
+
+
+
+ {/* Card 1 */}
+
+
+
+
+
+
+ Seamless integration with Prisma Client
+
+
+
+ When using Prisma Migrate with Prisma Client, schema changes are
+ type checked in your application code. This eliminates errors
+ that arise when database schema changes require changes to the
+ application code.
+
+
+
+ {/* Card 2 */}
+
+
+
+
+
+
+ Declarative data modelling
+
+
+
+ Prisma Migrate generates migrations based on changes in the
+ Prisma schema – a human-readable declarative definition of your
+ database schema. This allows you to focus on your desired
+ database schema rather than the steps to get there.
+
+
+
+ {/* Card 3 */}
+
+
+
+
+
+
+ Version control for your database
+
+
+
+ With Prisma Migrate, generated migrations are tracked in your
+ Git repository, allowing you to make changes to your database
+ schema in tandem with your application code.
+
+
+
+ {/* Card 4 */}
+
+
+
+
+
+
+ Streamlined collaboration
+
+
+
+ With Prisma Migrate, generated migrations are tracked in your
+ Git repository, allowing you to make changes to your database
+ schema in tandem with your application code.
+
+
+
+ {/* Card 5 */}
+
+
+
+
+
+
+ Bring your own project
+
+
+
+ Prisma Migrate can be adopted in any existing project that uses
+ PostgreSQL, MySQL, MariaDB, SQL Server, CockroachDB or SQLite.
+
+
+
+
+
+
+ );
+}
diff --git a/apps/site/src/components/migrate/hero-code.tsx b/apps/site/src/components/migrate/hero-code.tsx
new file mode 100644
index 0000000000..3e171a315c
--- /dev/null
+++ b/apps/site/src/components/migrate/hero-code.tsx
@@ -0,0 +1,209 @@
+"use client";
+
+import React, { useState, useEffect } from "react";
+import { Button, Card, CardHeader, CodeBlock } from "@prisma/eclipse";
+import { cn } from "@/lib/cn";
+import { prisma_highlighter as getHighlighter } from "@/lib/shiki_prisma";
+
+export interface HeroCodeStep {
+ title: string;
+ schema: string;
+ migrateFileName: string;
+ migrateFileContents: string;
+ arrowOffset: {
+ x: number;
+ y: number;
+ rotation: number;
+ };
+}
+
+interface HeroCodeProps {
+ steps: HeroCodeStep[];
+}
+
+const HeroCode: React.FC = ({ steps }) => {
+ const [activeStep, setActiveStep] = useState(0);
+ const [highlightedSchema, setHighlightedSchema] = useState("");
+ const [highlightedMigration, setHighlightedMigration] = useState("");
+ const [isLoading, setIsLoading] = useState(true);
+
+ // Load highlighted code when activeStep changes
+ useEffect(() => {
+ let isMounted = true;
+
+ const loadHighlightedCode = async () => {
+ setIsLoading(true);
+ try {
+ const highlighter = await getHighlighter();
+
+ const [schemaHtml, migrationHtml] = await Promise.all([
+ highlighter.codeToHtml(steps[activeStep].schema, {
+ lang: "prisma",
+ theme: "prisma-dark",
+ }),
+ highlighter.codeToHtml(steps[activeStep].migrateFileContents, {
+ lang: "sql",
+ theme: "prisma-dark",
+ }),
+ ]);
+
+ // Post-process to add diff styling for lines starting with +
+ const processedSchemaHtml = schemaHtml.replace(
+ /([\s\S]*?)(<\/span>)/g,
+ (match, content, closing) => {
+ // Check if line starts with + (accounting for any leading spans)
+ const startsWithPlus =
+ /]*>\+/.test(content) || content.trim().startsWith("+");
+ if (startsWithPlus) {
+ return `${content}${closing}`;
+ }
+ return match;
+ },
+ );
+
+ if (isMounted) {
+ setHighlightedSchema(processedSchemaHtml);
+ setHighlightedMigration(migrationHtml);
+ setIsLoading(false);
+ }
+ } catch (error) {
+ console.error("Failed to highlight code:", error);
+ if (isMounted) {
+ setIsLoading(false);
+ }
+ }
+ };
+
+ loadHighlightedCode();
+
+ return () => {
+ isMounted = false;
+ };
+ }, [activeStep, steps]);
+
+ // cycle across steps and go back to if we're at the end
+ const cycleActiveStep = () => {
+ activeStep === steps.length - 1
+ ? setActiveStep(0)
+ : setActiveStep(activeStep + 1);
+ };
+
+ return (
+
+ {/* First Code Card - schema.prisma */}
+
+ {/* Card Header */}
+
+
+ schema.prisma
+
+ cycleActiveStep()}
+ className={cn(
+ "w-fit transition-all duration-300",
+ activeStep !== 0 && "lg:mr-26",
+ )}
+ disabled={isLoading}
+ >
+ {steps[activeStep].title}
+
+
+ .absolute]:duration-300 [&>.absolute]:transition-all",
+ activeStep !== 0 && "[&>.absolute]:lg:mr-23",
+ )}
+ >
+ {isLoading ? (
+ Loading...
+ ) : (
+
+ )}
+
+
+
+ {/* Second Code Card - Migration file */}
+
+ {/* Card Header */}
+
+
+ {steps[activeStep].migrateFileName}
+
+
+
+ {isLoading ? (
+ Loading...
+ ) : (
+
+ )}
+
+
+
+ {/* Arrow SVG */}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default HeroCode;
diff --git a/apps/site/src/lib/cn.ts b/apps/site/src/lib/cn.ts
new file mode 100644
index 0000000000..ba66fd250b
--- /dev/null
+++ b/apps/site/src/lib/cn.ts
@@ -0,0 +1 @@
+export { twMerge as cn } from 'tailwind-merge';
diff --git a/apps/site/src/lib/shiki_prisma.ts b/apps/site/src/lib/shiki_prisma.ts
new file mode 100644
index 0000000000..df422158d3
--- /dev/null
+++ b/apps/site/src/lib/shiki_prisma.ts
@@ -0,0 +1,246 @@
+import { createHighlighter, type ThemeRegistration } from "shiki";
+
+const prismaTheme: ThemeRegistration = {
+ name: "prisma-dark",
+ type: "dark",
+ colors: {
+ "editor.background": "transparent",
+ "editor.foreground": "var(--color-foreground-neutral-weak)",
+ },
+ settings: [
+ {
+ scope: ["comment", "punctuation.definition.comment"],
+ settings: {
+ foreground: "var(--color-disabled)",
+ },
+ },
+ {
+ scope: [
+ "variable",
+ "variable.other.readwrite",
+ "variable.other.object",
+ "variable.other.property",
+ "support.variable",
+ ],
+ settings: {
+ foreground: "var(--color-foreground-neutral-weak)",
+ },
+ },
+ {
+ scope: [
+ "variable.other.constant",
+ "variable.language.this",
+ "variable.language.super",
+ ],
+ settings: {
+ foreground: "var(--color-background-ppg-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "keyword",
+ "keyword.control",
+ "keyword.operator.new",
+ "keyword.control.import",
+ "keyword.control.export",
+ "keyword.control.from",
+ "keyword.control.default",
+ "keyword.control.as",
+ "keyword.control.async",
+ "keyword.control.await",
+ "storage.type",
+ "storage.modifier",
+ "storage.type.function",
+ "storage.type.class",
+ "storage.type.const",
+ "storage.type.let",
+ "storage.type.var",
+ ],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "entity.name.function",
+ "meta.function-call",
+ "support.function",
+ "entity.name.method",
+ ],
+ settings: {
+ foreground: "var(--color-background-ppg-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "entity.name.type",
+ "entity.name.class",
+ "support.type",
+ "support.class",
+ "meta.import variable.other.readwrite",
+ "meta.export variable.other.readwrite",
+ ],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "string",
+ "string.quoted.single",
+ "string.quoted.double",
+ "string.template",
+ "punctuation.definition.string.begin",
+ "punctuation.definition.string.end",
+ ],
+ settings: {
+ foreground: "var(--color-background-ppg-reverse-strong)",
+ },
+ },
+ {
+ scope: ["constant.numeric", "constant.language", "constant.character"],
+ settings: {
+ foreground: "var(--color-background-warning-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "constant.language.boolean",
+ "constant.language.null",
+ "constant.language.undefined",
+ ],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "keyword.operator",
+ "keyword.operator.arithmetic",
+ "keyword.operator.assignment",
+ "keyword.operator.comparison",
+ "keyword.operator.logical",
+ ],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: ["keyword.operator.type", "keyword.operator.expression"],
+ settings: {
+ foreground: "var(--color-background-orm-reverse-strong)",
+ },
+ },
+ {
+ scope: [
+ "punctuation.accessor",
+ "punctuation.separator",
+ "punctuation.terminator",
+ "punctuation.definition.block",
+ "punctuation.definition.parameters",
+ "punctuation.definition.arguments",
+ "meta.brace.round",
+ "meta.brace.square",
+ "meta.brace.curly",
+ ],
+ settings: {
+ foreground: "var(--color-foreground-neutral-weak)",
+ },
+ },
+ {
+ scope: ["entity.name.tag", "punctuation.definition.tag"],
+ settings: {
+ foreground: "var(--color-foreground-neutral-weak)",
+ },
+ },
+ {
+ scope: ["entity.other.attribute-name"],
+ settings: {
+ foreground: "var(--color-background-ppg-reverse-strong)",
+ },
+ },
+ {
+ scope: ["meta.tag.attributes string.quoted"],
+ settings: {
+ foreground: "#96E072",
+ },
+ },
+ {
+ scope: [
+ "punctuation.definition.template-expression",
+ "punctuation.section.embedded",
+ ],
+ settings: {
+ foreground: "var(--color-foreground-neutral-weak)",
+ },
+ },
+ {
+ scope: ["meta.template.expression"],
+ settings: {
+ foreground: "#D5CED9",
+ },
+ },
+ {
+ scope: ["support.type.primitive", "support.type.builtin"],
+ settings: {
+ foreground: "#7cb7ff",
+ },
+ },
+ {
+ scope: ["variable.parameter", "meta.parameters variable"],
+ settings: {
+ foreground: "var(--color-background-warning-reverse-strong)",
+ },
+ },
+ {
+ scope: ["invalid", "invalid.illegal"],
+ settings: {
+ foreground: "#FC644D",
+ },
+ },
+ {
+ scope: ["markup.inserted", "markup.inserted.diff"],
+ settings: {
+ background: "var(--color-background-success-reverse-strong)",
+ },
+ },
+ {
+ scope: ["markup.deleted", "markup.deleted.diff"],
+ settings: {
+ background: "var(--color-background-error-reverse-strong)",
+ },
+ },
+ {
+ scope: ["meta.diff.header"],
+ settings: {
+ background: "var(--color-foreground-neutral-weak)",
+ },
+ },
+ ],
+};
+
+let prisma_highlighter: Awaited> | null =
+ null;
+
+async function getHighlighter() {
+ if (!prisma_highlighter) {
+ prisma_highlighter = await createHighlighter({
+ themes: [prismaTheme],
+ langs: [
+ "typescript",
+ "javascript",
+ "jsx",
+ "tsx",
+ "json",
+ "bash",
+ "sh",
+ "prisma",
+ "sql",
+ "diff",
+ ],
+ });
+ }
+ return prisma_highlighter;
+}
+
+export { getHighlighter as prisma_highlighter };
diff --git a/packages/eclipse/src/components/button.tsx b/packages/eclipse/src/components/button.tsx
index 061f56253c..3fe3dccae5 100644
--- a/packages/eclipse/src/components/button.tsx
+++ b/packages/eclipse/src/components/button.tsx
@@ -5,29 +5,36 @@ import { cn } from "../lib/cn";
const buttonVariants = cva(
"flex flex-row justify-center items-center rounded-square transition-all duration-50 cursor-pointer disabled:bg-background-neutral-weak! disabled:border-none! disabled:text-foreground-neutral-weaker! disabled:cursor-not-allowed disabled:shadow-none",
{
- variants: {
- variant: {
- ppg: "bg-background-ppg-reverse text-foreground-ppg-reverse hover:bg-background-ppg-reverse-strong shadow-box-low",
- orm: "bg-background-orm-reverse text-foreground-orm-reverse hover:bg-background-orm-reverse-strong shadow-box-low",
- "default-stronger": "bg-background-neutral text-foreground-neutral hover:bg-background-neutral-strong",
- default: "bg-background-default hover:bg-background-neutral border border-stroke-neutral hover:border-stroke-neutral-strong text-foreground-neutral shadow-box-low",
- "default-weaker": "bg-transparent hover:bg-background-neutral text-foreground-neutral",
- error: "bg-background-error-reverse text-foreground-error-reverse hover:bg-background-error-reverse-strong shadow-box-low",
- success: "bg-background-success-reverse text-foreground-success-reverse hover:bg-background-success-reverse-strong shadow-box-low",
- link: "text-foreground-neutral underline-offset-4 hover:underline focus-visible:ring-foreground-neutral",
+ variants: {
+ variant: {
+ ppg: "bg-background-ppg-reverse text-foreground-ppg-reverse hover:bg-background-ppg-reverse-strong shadow-box-low",
+ orm: "bg-background-orm-reverse text-foreground-orm-reverse hover:bg-background-orm-reverse-strong shadow-box-low",
+ "default-stronger":
+ "bg-background-neutral text-foreground-neutral hover:bg-background-neutral-strong",
+ default:
+ "bg-background-default hover:bg-background-neutral border border-stroke-neutral hover:border-stroke-neutral-strong text-foreground-neutral shadow-box-low",
+ "default-weaker":
+ "bg-transparent hover:bg-background-neutral text-foreground-neutral",
+ error:
+ "bg-background-error-reverse text-foreground-error-reverse hover:bg-background-error-reverse-strong shadow-box-low",
+ success:
+ "bg-background-success-reverse text-foreground-success-reverse hover:bg-background-success-reverse-strong shadow-box-low",
+ link: "text-foreground-neutral underline-offset-4 hover:underline focus-visible:ring-foreground-neutral",
+ },
+ size: {
+ lg: "px-2 h-element-lg type-text-sm-strong",
+ xl: "px-3 h-element-xl type-text-sm-strong",
+ "2xl": "px-3 h-element-2xl type-text-sm-strong",
+ "3xl": "px-3 h-element-3xl type-text-sm-strong",
+ "4xl": "px-4 h-element-4xl type-heading-md",
+ },
},
- size: {
- lg: "px-2 h-element-lg type-text-sm-strong",
- xl: "px-3 h-element-xl type-text-sm-strong",
- "2xl": "px-3 h-element-2xl type-text-sm-strong",
- "4xl": "px-4 h-element-4xl type-heading-md",
+ defaultVariants: {
+ variant: "default",
+ size: "lg",
},
},
- defaultVariants: {
- variant: "default",
- size: "lg",
- },
-});
+);
type ButtonBaseProps = VariantProps;
@@ -47,27 +54,27 @@ const Button = React.forwardRef<
HTMLButtonElement | HTMLAnchorElement,
ButtonProps
>(({ className, variant, size, href, ...props }, ref) => {
- const classNames = cn(buttonVariants({ variant, size, className }));
-
- if (href) {
- return (
- }
- href={href}
- {...(props as React.AnchorHTMLAttributes)}
- />
- );
- }
+ const classNames = cn(buttonVariants({ variant, size, className }));
+ if (href) {
return (
- }
- {...(props as React.ButtonHTMLAttributes)}
+ ref={ref as React.Ref}
+ href={href}
+ {...(props as React.AnchorHTMLAttributes)}
/>
);
- });
+ }
+
+ return (
+ }
+ {...(props as React.ButtonHTMLAttributes)}
+ />
+ );
+});
Button.displayName = "Button";
diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css
index 8c48df7f22..882da3a042 100644
--- a/packages/ui/src/styles/globals.css
+++ b/packages/ui/src/styles/globals.css
@@ -130,7 +130,6 @@
}
.stretch-display {
font-variation-settings:
- "wght" 700,
+ "wght" 900,
"wdth" 125;
}
-
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f11ecc6306..3541147185 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -588,6 +588,9 @@ importers:
'@prisma/eclipse':
specifier: workspace:^
version: link:../../packages/eclipse
+ clsx:
+ specifier: 'catalog:'
+ version: 2.1.1
cors:
specifier: ^2.8.6
version: 2.8.6
@@ -615,6 +618,12 @@ importers:
remark-directive:
specifier: 'catalog:'
version: 4.0.0
+ shiki:
+ specifier: 3.22.0
+ version: 3.22.0
+ tailwind-merge:
+ specifier: 'catalog:'
+ version: 3.5.0
zod:
specifier: 'catalog:'
version: 4.3.6