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 +

+
+ + {/**/} +
+
+
+
+ +
+
+
+
+
+ +
+ + + +

+ 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 + + + + .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 ( -