From 3110628333ee9d090aa7292f4b0e56de943d2ee0 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Thu, 11 Dec 2025 23:37:18 -0500 Subject: [PATCH 1/5] WIP --- .envrc | 1 + .gitignore | 5 +- .neon | 3 - .superset/setup.sh | 6 +- .superset/teardown.sh | 6 +- AGENTS.md | 9 +- apps/{blog => admin}/next-env.d.ts | 0 apps/admin/next.config.ts | 10 + apps/admin/package.json | 45 +++ apps/admin/postcss.config.mjs | 5 + apps/{website => admin}/public/favicon.ico | Bin apps/admin/src/app/globals.css | 4 + apps/admin/src/app/layout.tsx | 40 +++ apps/admin/src/app/page.tsx | 25 ++ apps/admin/src/app/providers.tsx | 22 ++ apps/admin/src/env.ts | 30 ++ apps/admin/src/trpc/query-client.ts | 20 ++ apps/admin/src/trpc/react.tsx | 60 ++++ apps/admin/src/trpc/server.tsx | 26 ++ apps/admin/tsconfig.json | 14 + apps/api/next-env.d.ts | 6 + apps/api/next.config.ts | 32 ++ apps/api/package.json | 33 ++ apps/api/public/favicon.ico | Bin 0 -> 41839 bytes apps/api/src/app/api/trpc/[trpc]/route.ts | 18 + apps/api/src/env.ts | 19 + apps/api/tsconfig.json | 20 ++ apps/blog/.gitignore | 41 --- apps/blog/README.md | 36 -- apps/blog/mdx-components.tsx | 5 - apps/blog/next.config.ts | 13 - apps/blog/package.json | 37 -- apps/blog/postcss.config.mjs | 7 - apps/blog/public/file.svg | 1 - apps/blog/public/globe.svg | 1 - apps/blog/public/next.svg | 1 - apps/blog/public/vercel.svg | 1 - apps/blog/public/window.svg | 1 - apps/blog/src/app/building-with-nextra.mdx | 60 ---- apps/blog/src/app/favicon.ico | Bin 15406 -> 0 bytes apps/blog/src/app/globals.css | 8 - apps/blog/src/app/hello-world.mdx | 34 -- apps/blog/src/app/layout.tsx | 39 --- apps/blog/src/app/page.mdx | 9 - apps/blog/src/components/Footer.tsx | 80 ----- apps/blog/src/components/Header.tsx | 142 -------- apps/blog/src/components/motion/FadeUp.tsx | 46 --- .../src/components/motion/HeroParallax.tsx | 63 ---- apps/blog/src/components/motion/TiltCard.tsx | 56 --- apps/blog/src/components/motion/index.ts | 3 - apps/blog/src/components/three/HeroCanvas.tsx | 327 ------------------ apps/blog/src/components/three/index.ts | 1 - apps/blog/tsconfig.json | 11 - apps/docs/package.json | 2 +- apps/docs/public/favicon.ico | Bin 0 -> 41839 bytes apps/marketing/next-env.d.ts | 6 + apps/marketing/next.config.ts | 11 + apps/marketing/package.json | 42 +++ apps/marketing/postcss.config.mjs | 5 + apps/marketing/public/favicon.ico | Bin 0 -> 41839 bytes .../public/hero/change-themes.gif | Bin .../public/hero/manage-terminals.gif | Bin .../public/hero/open-worktrees.gif | Bin .../public/hero/use-agents.gif | Bin apps/{website => marketing}/public/title.svg | 0 .../app/components/CTASection/CTASection.tsx | 0 .../src/app/components/CTASection/index.ts | 0 .../ClientLogosSection/ClientLogosSection.tsx | 0 .../ClientLogosSection/constants.ts | 0 .../components/ClientLogosSection/index.ts | 0 .../DownloadButton/DownloadButton.tsx | 0 .../app/components/DownloadButton/index.ts | 0 .../src/app/components/Footer/Footer.tsx | 0 .../src/app/components/Footer/index.ts | 0 .../src/app/components/Header/Header.tsx | 0 .../src/app/components/Header/index.ts | 0 .../components/HeroSection/HeroSection.tsx | 0 .../src/app/components/HeroSection/index.ts | 0 .../JoinWaitlistButton/JoinWaitlistButton.tsx | 0 .../components/JoinWaitlistButton/index.ts | 0 .../PlatformDropdown/PlatformDropdown.tsx | 0 .../app/components/PlatformDropdown/index.ts | 0 .../SecuritySection/SecuritySection.tsx | 0 .../app/components/SecuritySection/index.ts | 0 .../components/SocialLinks/SocialLinks.tsx | 0 .../src/app/components/SocialLinks/index.ts | 0 .../TrustedBySection/TrustedBySection.tsx | 0 .../app/components/TrustedBySection/index.ts | 0 .../components/VideoSection/VideoSection.tsx | 0 .../src/app/components/VideoSection/index.ts | 0 .../WaitlistModal/WaitlistModal.tsx | 0 .../src/app/components/WaitlistModal/index.ts | 0 .../src/app/globals.css | 0 .../{website => marketing}/src/app/layout.tsx | 3 +- apps/{website => marketing}/src/app/page.tsx | 0 .../src/app/privacy-policy/page.tsx | 0 .../src/app/scripts/page.tsx | 0 .../src/app/terms-of-service/page.tsx | 0 apps/{website => marketing}/src/constants.ts | 0 apps/{website => marketing}/src/env.ts | 11 +- apps/marketing/tsconfig.json | 20 ++ apps/web/next-env.d.ts | 6 + apps/web/next.config.ts | 10 + apps/web/package.json | 45 +++ apps/web/postcss.config.mjs | 5 + apps/web/public/favicon.ico | Bin 0 -> 41839 bytes apps/web/src/app/globals.css | 4 + apps/web/src/app/layout.tsx | 40 +++ apps/web/src/app/page.tsx | 60 ++++ apps/web/src/app/providers.tsx | 22 ++ apps/web/src/env.ts | 32 ++ apps/web/src/trpc/query-client.ts | 29 ++ apps/web/src/trpc/react.tsx | 62 ++++ apps/web/src/trpc/server.tsx | 26 ++ apps/web/tsconfig.json | 14 + apps/website/.gitignore | 41 --- apps/website/next.config.ts | 8 - apps/website/package.json | 45 --- apps/website/postcss.config.mjs | 7 - apps/website/src/app/api/trpc/[trpc]/route.ts | 34 -- apps/website/src/trpc/query-client.ts | 24 -- apps/website/src/trpc/react.tsx | 68 ---- apps/website/src/trpc/server.tsx | 52 --- apps/website/tsconfig.json | 11 - bun.lock | 137 ++++++-- packages/trpc/src/router/user.ts | 8 +- turbo.jsonc | 18 +- 127 files changed, 1011 insertions(+), 1379 deletions(-) create mode 100644 .envrc delete mode 100644 .neon rename apps/{blog => admin}/next-env.d.ts (100%) create mode 100644 apps/admin/next.config.ts create mode 100644 apps/admin/package.json create mode 100644 apps/admin/postcss.config.mjs rename apps/{website => admin}/public/favicon.ico (100%) create mode 100644 apps/admin/src/app/globals.css create mode 100644 apps/admin/src/app/layout.tsx create mode 100644 apps/admin/src/app/page.tsx create mode 100644 apps/admin/src/app/providers.tsx create mode 100644 apps/admin/src/env.ts create mode 100644 apps/admin/src/trpc/query-client.ts create mode 100644 apps/admin/src/trpc/react.tsx create mode 100644 apps/admin/src/trpc/server.tsx create mode 100644 apps/admin/tsconfig.json create mode 100644 apps/api/next-env.d.ts create mode 100644 apps/api/next.config.ts create mode 100644 apps/api/package.json create mode 100644 apps/api/public/favicon.ico create mode 100644 apps/api/src/app/api/trpc/[trpc]/route.ts create mode 100644 apps/api/src/env.ts create mode 100644 apps/api/tsconfig.json delete mode 100644 apps/blog/.gitignore delete mode 100644 apps/blog/README.md delete mode 100644 apps/blog/mdx-components.tsx delete mode 100644 apps/blog/next.config.ts delete mode 100644 apps/blog/package.json delete mode 100644 apps/blog/postcss.config.mjs delete mode 100644 apps/blog/public/file.svg delete mode 100644 apps/blog/public/globe.svg delete mode 100644 apps/blog/public/next.svg delete mode 100644 apps/blog/public/vercel.svg delete mode 100644 apps/blog/public/window.svg delete mode 100644 apps/blog/src/app/building-with-nextra.mdx delete mode 100644 apps/blog/src/app/favicon.ico delete mode 100644 apps/blog/src/app/globals.css delete mode 100644 apps/blog/src/app/hello-world.mdx delete mode 100644 apps/blog/src/app/layout.tsx delete mode 100644 apps/blog/src/app/page.mdx delete mode 100644 apps/blog/src/components/Footer.tsx delete mode 100644 apps/blog/src/components/Header.tsx delete mode 100644 apps/blog/src/components/motion/FadeUp.tsx delete mode 100644 apps/blog/src/components/motion/HeroParallax.tsx delete mode 100644 apps/blog/src/components/motion/TiltCard.tsx delete mode 100644 apps/blog/src/components/motion/index.ts delete mode 100644 apps/blog/src/components/three/HeroCanvas.tsx delete mode 100644 apps/blog/src/components/three/index.ts delete mode 100644 apps/blog/tsconfig.json create mode 100644 apps/docs/public/favicon.ico create mode 100644 apps/marketing/next-env.d.ts create mode 100644 apps/marketing/next.config.ts create mode 100644 apps/marketing/package.json create mode 100644 apps/marketing/postcss.config.mjs create mode 100644 apps/marketing/public/favicon.ico rename apps/{website => marketing}/public/hero/change-themes.gif (100%) rename apps/{website => marketing}/public/hero/manage-terminals.gif (100%) rename apps/{website => marketing}/public/hero/open-worktrees.gif (100%) rename apps/{website => marketing}/public/hero/use-agents.gif (100%) rename apps/{website => marketing}/public/title.svg (100%) rename apps/{website => marketing}/src/app/components/CTASection/CTASection.tsx (100%) rename apps/{website => marketing}/src/app/components/CTASection/index.ts (100%) rename apps/{website => marketing}/src/app/components/ClientLogosSection/ClientLogosSection.tsx (100%) rename apps/{website => marketing}/src/app/components/ClientLogosSection/constants.ts (100%) rename apps/{website => marketing}/src/app/components/ClientLogosSection/index.ts (100%) rename apps/{website => marketing}/src/app/components/DownloadButton/DownloadButton.tsx (100%) rename apps/{website => marketing}/src/app/components/DownloadButton/index.ts (100%) rename apps/{website => marketing}/src/app/components/Footer/Footer.tsx (100%) rename apps/{website => marketing}/src/app/components/Footer/index.ts (100%) rename apps/{website => marketing}/src/app/components/Header/Header.tsx (100%) rename apps/{website => marketing}/src/app/components/Header/index.ts (100%) rename apps/{website => marketing}/src/app/components/HeroSection/HeroSection.tsx (100%) rename apps/{website => marketing}/src/app/components/HeroSection/index.ts (100%) rename apps/{website => marketing}/src/app/components/JoinWaitlistButton/JoinWaitlistButton.tsx (100%) rename apps/{website => marketing}/src/app/components/JoinWaitlistButton/index.ts (100%) rename apps/{website => marketing}/src/app/components/PlatformDropdown/PlatformDropdown.tsx (100%) rename apps/{website => marketing}/src/app/components/PlatformDropdown/index.ts (100%) rename apps/{website => marketing}/src/app/components/SecuritySection/SecuritySection.tsx (100%) rename apps/{website => marketing}/src/app/components/SecuritySection/index.ts (100%) rename apps/{website => marketing}/src/app/components/SocialLinks/SocialLinks.tsx (100%) rename apps/{website => marketing}/src/app/components/SocialLinks/index.ts (100%) rename apps/{website => marketing}/src/app/components/TrustedBySection/TrustedBySection.tsx (100%) rename apps/{website => marketing}/src/app/components/TrustedBySection/index.ts (100%) rename apps/{website => marketing}/src/app/components/VideoSection/VideoSection.tsx (100%) rename apps/{website => marketing}/src/app/components/VideoSection/index.ts (100%) rename apps/{website => marketing}/src/app/components/WaitlistModal/WaitlistModal.tsx (100%) rename apps/{website => marketing}/src/app/components/WaitlistModal/index.ts (100%) rename apps/{website => marketing}/src/app/globals.css (100%) rename apps/{website => marketing}/src/app/layout.tsx (92%) rename apps/{website => marketing}/src/app/page.tsx (100%) rename apps/{website => marketing}/src/app/privacy-policy/page.tsx (100%) rename apps/{website => marketing}/src/app/scripts/page.tsx (100%) rename apps/{website => marketing}/src/app/terms-of-service/page.tsx (100%) rename apps/{website => marketing}/src/constants.ts (100%) rename apps/{website => marketing}/src/env.ts (66%) create mode 100644 apps/marketing/tsconfig.json create mode 100644 apps/web/next-env.d.ts create mode 100644 apps/web/next.config.ts create mode 100644 apps/web/package.json create mode 100644 apps/web/postcss.config.mjs create mode 100644 apps/web/public/favicon.ico create mode 100644 apps/web/src/app/globals.css create mode 100644 apps/web/src/app/layout.tsx create mode 100644 apps/web/src/app/page.tsx create mode 100644 apps/web/src/app/providers.tsx create mode 100644 apps/web/src/env.ts create mode 100644 apps/web/src/trpc/query-client.ts create mode 100644 apps/web/src/trpc/react.tsx create mode 100644 apps/web/src/trpc/server.tsx create mode 100644 apps/web/tsconfig.json delete mode 100644 apps/website/.gitignore delete mode 100644 apps/website/next.config.ts delete mode 100644 apps/website/package.json delete mode 100644 apps/website/postcss.config.mjs delete mode 100644 apps/website/src/app/api/trpc/[trpc]/route.ts delete mode 100644 apps/website/src/trpc/query-client.ts delete mode 100644 apps/website/src/trpc/react.tsx delete mode 100644 apps/website/src/trpc/server.tsx delete mode 100644 apps/website/tsconfig.json diff --git a/.envrc b/.envrc new file mode 100644 index 00000000000..fe7c01aa90e --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +dotenv diff --git a/.gitignore b/.gitignore index 22dcc12a13b..3131b454638 100644 --- a/.gitignore +++ b/.gitignore @@ -45,7 +45,10 @@ mise.toml # Turbo .turbo .cache -.envrc + +# Next.js +.next +out # Superset # Ignore .superset directory except for config and scripts diff --git a/.neon b/.neon deleted file mode 100644 index fc48de70460..00000000000 --- a/.neon +++ /dev/null @@ -1,3 +0,0 @@ -{ - "projectId": "tiny-cherry-82420694" -} diff --git a/.superset/setup.sh b/.superset/setup.sh index 1c0e7a21aec..1badfdf0f24 100755 --- a/.superset/setup.sh +++ b/.superset/setup.sh @@ -15,6 +15,10 @@ command -v bun &> /dev/null || error "Bun not installed. Install from https://bu command -v neonctl &> /dev/null || error "Neon CLI not installed. Run: npm install -g neonctl" command -v jq &> /dev/null || error "jq not installed. Run: brew install jq" +# Check required environment variables +NEON_PROJECT_ID="${NEON_PROJECT_ID:-}" +[ -z "$NEON_PROJECT_ID" ] && error "NEON_PROJECT_ID environment variable is required" + # Install dependencies echo "๐Ÿ“ฅ Installing dependencies..." bun install @@ -34,7 +38,7 @@ fi echo "๐Ÿ—„๏ธ Creating Neon branch..." WORKSPACE_NAME="${SUPERSET_WORKSPACE_NAME:-$(basename "$PWD")}" NEON_OUTPUT=$(neonctl branches create \ - --project-id tiny-cherry-82420694 \ + --project-id "$NEON_PROJECT_ID" \ --name "$WORKSPACE_NAME" \ --output json) diff --git a/.superset/teardown.sh b/.superset/teardown.sh index 14269a20f4d..2b4bf203171 100755 --- a/.superset/teardown.sh +++ b/.superset/teardown.sh @@ -13,6 +13,10 @@ echo "๐Ÿงน Tearing down Superset workspace..." # Check dependencies command -v neonctl &> /dev/null || error "Neon CLI not installed. Run: npm install -g neonctl" +# Check required environment variables +NEON_PROJECT_ID="${NEON_PROJECT_ID:-}" +[ -z "$NEON_PROJECT_ID" ] && error "NEON_PROJECT_ID environment variable is required" + # Delete Neon branch for this workspace WORKSPACE_NAME="${SUPERSET_WORKSPACE_NAME:-$(basename "$PWD")}" if [ -f ".env" ]; then @@ -25,7 +29,7 @@ if [ -z "$BRANCH_ID" ]; then fi echo "๐Ÿ—„๏ธ Deleting Neon branch: $WORKSPACE_NAME ($BRANCH_ID)" -if neonctl branches delete "$BRANCH_ID" --project-id tiny-cherry-82420694 --force 2>/dev/null; then +if neonctl branches delete "$BRANCH_ID" --project-id "$NEON_PROJECT_ID" --force 2>/dev/null; then success "Neon branch deleted: $WORKSPACE_NAME" else echo "โš ๏ธ Neon branch '$WORKSPACE_NAME' ($BRANCH_ID) not found or already deleted" diff --git a/AGENTS.md b/AGENTS.md index 81e5485c4b1..6a171f37049 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,10 +6,12 @@ Guidelines for agents and developers working in this repository. Bun + Turbo monorepo with: - **Apps**: - - `apps/website` - Main website application + - `apps/web` - Main web application (app.superset.sh) + - `apps/marketing` - Marketing site (superset.sh) + - `apps/admin` - Admin dashboard + - `apps/api` - API backend - `apps/desktop` - Electron desktop application (see [Desktop App Guide](#desktop-app-electron) below) - `apps/docs` - Documentation site - - `apps/blog` - Blog site - **Packages**: - `packages/ui` - Shared UI components (shadcn/ui + TailwindCSS v4). - Add components: `npx shadcn@latest add ` (run in `packages/ui/`) @@ -151,7 +153,8 @@ The `src/components/ui/`, `src/components/ai-elements`, and `src/components/reac - Always spin up a new neon branch to create migrations. Update our root .env files to point at the neon branch locally. - Use drizzle to manage the migration. You can see the schema at packages/db/src/schema. Never run a migration yourself. - Create migrations by changing drizzle schema then running `pnpm drizzle-kit generate --name=""` -- Neon org id is org-round-base-25422821, Neon project id is tiny-cherry-82420694. list_projects tool requires org_id passed in. +- `NEON_ORG_ID` and `NEON_PROJECT_ID` env vars are set in .env +- list_projects tool requires org_id passed in ## Desktop App (Electron) diff --git a/apps/blog/next-env.d.ts b/apps/admin/next-env.d.ts similarity index 100% rename from apps/blog/next-env.d.ts rename to apps/admin/next-env.d.ts diff --git a/apps/admin/next.config.ts b/apps/admin/next.config.ts new file mode 100644 index 00000000000..c9c33d2ed02 --- /dev/null +++ b/apps/admin/next.config.ts @@ -0,0 +1,10 @@ +import type { NextConfig } from "next"; + +const config: NextConfig = { + experimental: { + reactCompiler: true, + }, + typescript: { ignoreBuildErrors: true }, +}; + +export default config; diff --git a/apps/admin/package.json b/apps/admin/package.json new file mode 100644 index 00000000000..dd8633d9eb7 --- /dev/null +++ b/apps/admin/package.json @@ -0,0 +1,45 @@ +{ + "name": "@superset/admin", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "git clean -xdf .cache .next .turbo node_modules", + "dev": "next dev --port 3003", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@superset/db": "workspace:*", + "@superset/queries": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", + "@superset/ui": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query-devtools": "^5.90.10", + "@trpc/client": "^11.7.1", + "@trpc/server": "^11.7.1", + "@trpc/tanstack-react-query": "^11.7.1", + "geist": "^1.5.1", + "lucide-react": "^0.560.0", + "next": "^15.5.7", + "next-themes": "^0.4.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "server-only": "^0.0.1", + "superjson": "^2.2.5", + "zod": "^4.1.13" + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@tailwindcss/postcss": "^4.0.9", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "babel-plugin-react-compiler": "^1.0.0", + "tailwindcss": "^4.0.9", + "typescript": "^5.9.3" + } +} diff --git a/apps/admin/postcss.config.mjs b/apps/admin/postcss.config.mjs new file mode 100644 index 00000000000..c2ddf748220 --- /dev/null +++ b/apps/admin/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/apps/website/public/favicon.ico b/apps/admin/public/favicon.ico similarity index 100% rename from apps/website/public/favicon.ico rename to apps/admin/public/favicon.ico diff --git a/apps/admin/src/app/globals.css b/apps/admin/src/app/globals.css new file mode 100644 index 00000000000..ee297b51232 --- /dev/null +++ b/apps/admin/src/app/globals.css @@ -0,0 +1,4 @@ +@import "tailwindcss"; + +@import "@superset/ui/globals.css"; +@source "../../../../packages/ui/src/**/*.{ts,tsx}"; diff --git a/apps/admin/src/app/layout.tsx b/apps/admin/src/app/layout.tsx new file mode 100644 index 00000000000..564c4756248 --- /dev/null +++ b/apps/admin/src/app/layout.tsx @@ -0,0 +1,40 @@ +import { cn } from "@superset/ui/utils"; +import { Toaster } from "@superset/ui/sonner"; +import { GeistMono } from "geist/font/mono"; +import { GeistSans } from "geist/font/sans"; +import type { Metadata, Viewport } from "next"; + +import "./globals.css"; + +import { Providers } from "./providers"; + +export const metadata: Metadata = { + title: "Superset Admin", + description: "Admin dashboard for Superset", +}; + +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + + ); +} diff --git a/apps/admin/src/app/page.tsx b/apps/admin/src/app/page.tsx new file mode 100644 index 00000000000..5a134547095 --- /dev/null +++ b/apps/admin/src/app/page.tsx @@ -0,0 +1,25 @@ +import { api } from "@/trpc/server"; + +export default async function Home() { + const users = await (await api()).user.all.query(); + + return ( +
+

Superset Admin

+

Admin dashboard

+
+

tRPC Test Query

+

+ Users in database: {users.length} +

+ {users.length > 0 && ( +
    + {users.slice(0, 5).map((user) => ( +
  • {user.email}
  • + ))} +
+ )} +
+
+ ); +} diff --git a/apps/admin/src/app/providers.tsx b/apps/admin/src/app/providers.tsx new file mode 100644 index 00000000000..bf3658269f0 --- /dev/null +++ b/apps/admin/src/app/providers.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ThemeProvider } from "next-themes"; + +import { TRPCReactProvider } from "../trpc/react"; + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + + ); +} diff --git a/apps/admin/src/env.ts b/apps/admin/src/env.ts new file mode 100644 index 00000000000..1ac5058d2f9 --- /dev/null +++ b/apps/admin/src/env.ts @@ -0,0 +1,30 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { vercel } from "@t3-oss/env-nextjs/presets-zod"; +import { z } from "zod"; + +export const env = createEnv({ + extends: [vercel()], + shared: { + NODE_ENV: z.enum(["development", "production", "test"]).default("development"), + }, + + server: { + // Database (needed by @superset/trpc dependency) + DATABASE_URL: z.string().url(), + DATABASE_URL_UNPOOLED: z.string().url(), + }, + + client: { + NEXT_PUBLIC_API_URL: z.string().url(), + NEXT_PUBLIC_WEB_URL: z.string().url().optional(), + }, + + experimental__runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL, + }, + + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + emptyStringAsUndefined: true, +}); diff --git a/apps/admin/src/trpc/query-client.ts b/apps/admin/src/trpc/query-client.ts new file mode 100644 index 00000000000..7f3e4e1cfd0 --- /dev/null +++ b/apps/admin/src/trpc/query-client.ts @@ -0,0 +1,20 @@ +import { defaultShouldDehydrateQuery, QueryClient } from "@tanstack/react-query"; +import SuperJSON from "superjson"; + +export const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30 * 1000, + }, + dehydrate: { + serializeData: SuperJSON.serialize, + shouldDehydrateQuery: (query) => + defaultShouldDehydrateQuery(query) || query.state.status === "pending", + shouldRedactErrors: () => false, + }, + hydrate: { + deserializeData: SuperJSON.deserialize, + }, + }, + }); diff --git a/apps/admin/src/trpc/react.tsx b/apps/admin/src/trpc/react.tsx new file mode 100644 index 00000000000..5132f0d018e --- /dev/null +++ b/apps/admin/src/trpc/react.tsx @@ -0,0 +1,60 @@ +"use client"; + +import type { AppRouter } from "@superset/trpc"; +import type { QueryClient } from "@tanstack/react-query"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { createTRPCClient, httpBatchStreamLink, loggerLink } from "@trpc/client"; +import { createTRPCContext } from "@trpc/tanstack-react-query"; +import { useState } from "react"; +import SuperJSON from "superjson"; + +import { env } from "../env"; +import { createQueryClient } from "./query-client"; + +let clientQueryClientSingleton: QueryClient | undefined; +const getQueryClient = () => { + if (typeof window === "undefined") { + return createQueryClient(); + } + if (!clientQueryClientSingleton) { + clientQueryClientSingleton = createQueryClient(); + } + return clientQueryClientSingleton; +}; + +const context = createTRPCContext(); +export const { useTRPC, TRPCProvider } = context; +export type UseTRPC = typeof useTRPC; + +export function TRPCReactProvider(props: { children: React.ReactNode }) { + const queryClient = getQueryClient(); + + const [trpcClient] = useState(() => + createTRPCClient({ + links: [ + loggerLink({ + enabled: (op) => + env.NODE_ENV === "development" || + (op.direction === "down" && op.result instanceof Error), + }), + httpBatchStreamLink({ + transformer: SuperJSON, + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + const headers = new Headers(); + headers.set("x-trpc-source", "nextjs-react"); + return headers; + }, + }), + ], + }), + ); + + return ( + + + {props.children} + + + ); +} diff --git a/apps/admin/src/trpc/server.tsx b/apps/admin/src/trpc/server.tsx new file mode 100644 index 00000000000..d842794bc2a --- /dev/null +++ b/apps/admin/src/trpc/server.tsx @@ -0,0 +1,26 @@ +import "server-only"; + +import type { AppRouter } from "@superset/trpc"; +import { createTRPCClient, httpBatchLink } from "@trpc/client"; +import { headers } from "next/headers"; +import { cache } from "react"; +import SuperJSON from "superjson"; + +import { env } from "../env"; + +export const api = cache(async () => { + const heads = new Headers(await headers()); + heads.set("x-trpc-source", "rsc"); + + return createTRPCClient({ + links: [ + httpBatchLink({ + transformer: SuperJSON, + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + return Object.fromEntries(heads.entries()); + }, + }), + ], + }); +}); diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json new file mode 100644 index 00000000000..87769fd1895 --- /dev/null +++ b/apps/admin/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@superset/typescript/base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "plugins": [{ "name": "next" }] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/api/next-env.d.ts b/apps/api/next-env.d.ts new file mode 100644 index 00000000000..830fb594ca2 --- /dev/null +++ b/apps/api/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/api/next.config.ts b/apps/api/next.config.ts new file mode 100644 index 00000000000..b5e7fa7cbbf --- /dev/null +++ b/apps/api/next.config.ts @@ -0,0 +1,32 @@ +import type { NextConfig } from "next"; + +import { env } from "./src/env"; + +// Allowed origins for CORS +const allowedOrigins = [env.NEXT_PUBLIC_WEB_URL, env.NEXT_PUBLIC_ADMIN_URL].filter( + Boolean, +) as string[]; + +const config: NextConfig = { + experimental: { + reactCompiler: true, + }, + typescript: { ignoreBuildErrors: true }, + async headers() { + // Generate CORS headers for each allowed origin + return allowedOrigins.map((origin) => ({ + source: "/api/:path*", + headers: [ + { key: "Access-Control-Allow-Origin", value: origin }, + { key: "Access-Control-Allow-Methods", value: "GET, POST, OPTIONS" }, + { + key: "Access-Control-Allow-Headers", + value: "Content-Type, Authorization, trpc-accept, x-trpc-source", + }, + { key: "Access-Control-Allow-Credentials", value: "true" }, + ], + })); + }, +}; + +export default config; diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 00000000000..745ee77572a --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,33 @@ +{ + "name": "@superset/api", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "git clean -xdf .cache .next .turbo node_modules", + "dev": "next dev --port 3001", + "start": "next start --port 3001", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@superset/db": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "@trpc/server": "^11.7.1", + "drizzle-orm": "0.45.1", + "next": "^15.5.7", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "zod": "^4.1.13" + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "^5.9.3" + } +} diff --git a/apps/api/public/favicon.ico b/apps/api/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9c3517c0d4eb3ac1774dc5ea52e63d5ae7f8601e GIT binary patch literal 41839 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 z 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 literal 0 HcmV?d00001 diff --git a/apps/api/src/app/api/trpc/[trpc]/route.ts b/apps/api/src/app/api/trpc/[trpc]/route.ts new file mode 100644 index 00000000000..f2d09fcc734 --- /dev/null +++ b/apps/api/src/app/api/trpc/[trpc]/route.ts @@ -0,0 +1,18 @@ +import { appRouter, createTRPCContext } from "@superset/trpc"; +import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; + +const handler = (req: Request) => + fetchRequestHandler({ + endpoint: "/api/trpc", + req, + router: appRouter, + createContext: () => createTRPCContext({ headers: req.headers }), + onError: ({ path, error }) => { + console.error(`โŒ tRPC error on ${path ?? ""}:`, error); + }, + }); + +// Preflight requests - CORS headers added by next.config.ts +export const OPTIONS = () => new Response(null, { status: 204 }); + +export { handler as GET, handler as POST }; diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts new file mode 100644 index 00000000000..1bcb3ebc75a --- /dev/null +++ b/apps/api/src/env.ts @@ -0,0 +1,19 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { z } from "zod"; + +export const env = createEnv({ + server: { + DATABASE_URL: z.string(), + DATABASE_URL_UNPOOLED: z.string(), + }, + client: { + NEXT_PUBLIC_WEB_URL: z.string().url(), + NEXT_PUBLIC_ADMIN_URL: z.string().url().optional(), + }, + experimental__runtimeEnv: { + NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL, + NEXT_PUBLIC_ADMIN_URL: process.env.NEXT_PUBLIC_ADMIN_URL, + }, + emptyStringAsUndefined: true, + skipValidation: !!process.env.SKIP_ENV_VALIDATION, +}); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 00000000000..fec43826096 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@superset/typescript/base.json", + "compilerOptions": { + "plugins": [ + { + "name": "next" + } + ], + "module": "ESNext", + "moduleResolution": "Bundler", + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/blog/.gitignore b/apps/blog/.gitignore deleted file mode 100644 index 5ef6a520780..00000000000 --- a/apps/blog/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/apps/blog/README.md b/apps/blog/README.md deleted file mode 100644 index e215bc4ccf1..00000000000 --- a/apps/blog/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/apps/blog/mdx-components.tsx b/apps/blog/mdx-components.tsx deleted file mode 100644 index 34a2b1202cf..00000000000 --- a/apps/blog/mdx-components.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { useMDXComponents as getBlogMDXComponents } from "nextra-theme-blog"; - -export function useMDXComponents(components = {}) { - return getBlogMDXComponents(components); -} diff --git a/apps/blog/next.config.ts b/apps/blog/next.config.ts deleted file mode 100644 index 3856fd85073..00000000000 --- a/apps/blog/next.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { NextConfig } from "next"; -import nextra from "nextra"; - -const withNextra = nextra({ - defaultShowCopyCode: true, -}); - -const nextConfig: NextConfig = { - reactStrictMode: true, - transpilePackages: ["@superset/ui"], -}; - -export default withNextra(nextConfig); diff --git a/apps/blog/package.json b/apps/blog/package.json deleted file mode 100644 index 51ce6ff3829..00000000000 --- a/apps/blog/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@superset/blog", - "version": "0.1.0", - "private": true, - "scripts": { - "clean": "git clean -xdf .cache .next .turbo node_modules", - "dev": "next dev --port 3002", - "build": "next build", - "start": "next start", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@react-three/drei": "^10.7.6", - "@react-three/fiber": "^9.4.0", - "@superset/ui": "workspace:*", - "framer-motion": "^12.23.24", - "geist": "^1.5.1", - "next": "^15.5.7", - "nextra": "^4.6.0", - "nextra-theme-blog": "^4.6.0", - "nextra-theme-docs": "^4.6.0", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "three": "^0.181.2" - }, - "devDependencies": { - "@superset/typescript": "workspace:*", - "@tailwindcss/postcss": "^4.0.9", - "@types/mdx": "^2.0.13", - "@types/node": "^24.9.1", - "@types/react": "^19.1.11", - "@types/react-dom": "^19.1.7", - "@types/three": "^0.181.0", - "tailwindcss": "^4.0.9", - "typescript": "^5.9.3" - } -} diff --git a/apps/blog/postcss.config.mjs b/apps/blog/postcss.config.mjs deleted file mode 100644 index c42f31cab8a..00000000000 --- a/apps/blog/postcss.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -const config = { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; - -export default config; diff --git a/apps/blog/public/file.svg b/apps/blog/public/file.svg deleted file mode 100644 index 004145cddf3..00000000000 --- a/apps/blog/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/blog/public/globe.svg b/apps/blog/public/globe.svg deleted file mode 100644 index 567f17b0d7c..00000000000 --- a/apps/blog/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/blog/public/next.svg b/apps/blog/public/next.svg deleted file mode 100644 index 5174b28c565..00000000000 --- a/apps/blog/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/blog/public/vercel.svg b/apps/blog/public/vercel.svg deleted file mode 100644 index 77053960334..00000000000 --- a/apps/blog/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/blog/public/window.svg b/apps/blog/public/window.svg deleted file mode 100644 index b2b2a44f6eb..00000000000 --- a/apps/blog/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/blog/src/app/building-with-nextra.mdx b/apps/blog/src/app/building-with-nextra.mdx deleted file mode 100644 index d0056bd055f..00000000000 --- a/apps/blog/src/app/building-with-nextra.mdx +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Building Documentation and Blogs with Nextra -date: 2025-01-20 -description: How we integrated Nextra for our documentation and blog -author: Superset Team ---- - -# Building with Nextra - -Today we're excited to share how we integrated Nextra into our project for both documentation and blog content. - -## Why Nextra? - -Nextra is a powerful Next.js-based framework that makes it easy to create beautiful documentation and blog sites: - -- **MDX Support** - Write content in Markdown with React components -- **Built-in Themes** - Docs and blog themes ready to use -- **Search** - Full-text search out of the box -- **Fast** - Built on Next.js App Router for optimal performance - -## Our Setup - -We've created two separate routes: - -### Documentation (`/docs`) - -Our documentation uses the `nextra-theme-docs` theme with: - -```tsx -import { Layout, Navbar, Footer } from "nextra-theme-docs"; -``` - -This provides a clean, organized layout perfect for technical documentation. - -### Blog (`/blog`) - -Our blog uses the `nextra-theme-blog` theme: - -```tsx -import { Layout, Navbar, Footer, ThemeSwitch } from "nextra-theme-blog"; -``` - -This gives us a beautiful blog interface with features like: -- Post listings -- Dark mode support -- RSS feed support -- Tag/category organization - -## Benefits - -The best part about this setup is that we can: - -1. **Write in Markdown** - No complex CMS needed -2. **Version Control** - All content lives in Git -3. **Type Safety** - TypeScript support throughout -4. **Fast Builds** - Optimized by Turborepo - -## Try It Yourself - -Check out the [Nextra documentation](https://nextra.site) to learn more about building with this amazing framework. diff --git a/apps/blog/src/app/favicon.ico b/apps/blog/src/app/favicon.ico deleted file mode 100644 index 6922b9cfbce3642419e7bdf4f73c196bb4dc53aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 zcmeHNTPS5;7+>y}!YJc`2fR#EZj&N~3=w(oA}>fN`G+Us#f!)-@gUb|iiDJsJGnmi zhZK@Lh>|xU_e(Cn+25Ku+MPLP=G@rl%&b+v_CD+Ewb!@4@3+7G?e%?(Suq>-;RBP7 zzU)6+#=;n5zP>+xe|Bf=o7DH^%g_HmG4}sQ#y(5eNJFF%Kl-JEwg%}B0hta)Mn*6` zK8}fr369ChNlZ;m!PeGRCp}eGRziiMq9S;CdFeDxWryJ4U<3sPAuuoyiHV7ro0~&O zNQhqdP~D4;9v&WCetDn1=cA*eu(7c*NCqn_D=aK57&MmVEH5udX=$lW<7uwp;bEw3 zI6ps+!oosOzrJh!N3NHaX?P<)<168-1oBV6X+r-pA0z2W=tJm0a6pX%2L}f@Iy%xB zPEJlH)nP?N1*)p5P*YRGQCnM!y1F`!`uchh8X6in8XFs7XJ=>9`5he{p$XR3)+U`t zoBw%!euk#@`1pwR^>yUt<{BIaXq!{spP8A7jEsy|q^GANEiH||!^g(P@bdD4nVA{5 zxVXHFKIF2|c~VjmuCA^yJw1*0vWdD3$;rvwL|Iu`is*pknVp@*^767`^A{Hv%i-zaTuZNH$sy&XeCLpVJ>#naOhs;jHtg!kmnR#sN9wzkH7{l>-ywzjsg zy}gaq)m1b%H}mh(x3sOexA-m)9QZ3aAer^wZM07!ZPBtU31Xk*ujIGM;{k~(;+{eW zf&+pBf&+pBf&+ix0DZe|Zf+V4S65d>bbWJkqiO3^?>;v-H$~*99Cc`DC`VXW7{bHD zUl9=zfyl^6j;N?8L`O%zA~rS_D#XRb>6~Xbb$mqoT7QB$=aJ=k3swF?{Yd!@%7@3q z#DMm*Oszv>?nirDy0rWIdmJAhV{vg2#3iNn;nmmL)zyX0&Q5f6bb$8kyoi;kk^nvoqx6=Xc%)hn$-KY656Ur8u20o(Nk_^@ zQBx`>VyvzL0s{DRO#aByo&j3BpPwJE86X=dl^Zt31|uZbTPhMUg*S(>2ipJ>9*s&Bl?C$Pz`3njPEXsdUtqaxIP}|$vgO87o#mG;&f2!}j zxVYePP)SJ%?CtF>LVkMZc6WF48UoUNZ*Q;SekUa*1;0aTYAW*c^U>em&wUNaO*Ie{ zbDO#jOW$Xbz8m#v6zeQ4ErIGt%pqwpz5^exR$2f6 diff --git a/apps/blog/src/app/globals.css b/apps/blog/src/app/globals.css deleted file mode 100644 index a904e3aa5ee..00000000000 --- a/apps/blog/src/app/globals.css +++ /dev/null @@ -1,8 +0,0 @@ -@import "tailwindcss"; -@import "@superset/ui/globals.css"; - -@theme { - --font-family-sans: - var(--font-geist-sans), ui-sans-serif, system-ui, sans-serif; - --font-family-mono: var(--font-geist-mono), ui-monospace, monospace; -} diff --git a/apps/blog/src/app/hello-world.mdx b/apps/blog/src/app/hello-world.mdx deleted file mode 100644 index c86e32afa63..00000000000 --- a/apps/blog/src/app/hello-world.mdx +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Hello World - Welcome to Superset Blog -date: 2025-01-15 -description: Our first blog post introducing Superset and what's to come -author: Superset Team ---- - -# Hello World - -Welcome to the Superset blog! We're excited to share our journey with you. - -## What is Superset? - -Superset is a modern web development platform built with the latest technologies: - -- **Next.js 15** - The React framework for production -- **TailwindCSS v4** - Utility-first CSS framework -- **Turborepo** - High-performance build system -- **TypeScript** - Type-safe development - -## What to Expect - -In this blog, we'll share: - -1. **Technical insights** - Deep dives into our architecture and design decisions -2. **Updates** - New features and improvements -3. **Tutorials** - How-to guides and best practices -4. **Community** - Highlighting contributions and use cases - -## Get Started - -Check out our [documentation](/docs) to learn more about using Superset in your projects. - -Stay tuned for more exciting content! diff --git a/apps/blog/src/app/layout.tsx b/apps/blog/src/app/layout.tsx deleted file mode 100644 index a34f506098f..00000000000 --- a/apps/blog/src/app/layout.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Banner, Search } from "nextra/components"; -import { getPageMap } from "nextra/page-map"; -import { Footer, Layout, Navbar, ThemeSwitch } from "nextra-theme-blog"; -import type * as React from "react"; -import "nextra-theme-blog/style.css"; - -export const metadata = { - title: "Superset Blog", - description: "Latest news and updates from Superset", -}; - -const banner = ( - - Welcome to the Superset Blog - -); - -export default async function BlogLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - - - - - - - {children} -
-

ยฉ {new Date().getFullYear()} Superset. All rights reserved.

-
-
- - - ); -} diff --git a/apps/blog/src/app/page.mdx b/apps/blog/src/app/page.mdx deleted file mode 100644 index 28256fcee40..00000000000 --- a/apps/blog/src/app/page.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -type: posts ---- - -# Superset Blog - -Welcome to the Superset blog! Here you'll find the latest news, updates, and insights about Superset. - -Stay tuned for exciting content about web development, best practices, and new features. diff --git a/apps/blog/src/components/Footer.tsx b/apps/blog/src/components/Footer.tsx deleted file mode 100644 index a87e398c052..00000000000 --- a/apps/blog/src/components/Footer.tsx +++ /dev/null @@ -1,80 +0,0 @@ -"use client"; - -import { motion } from "framer-motion"; - -const FOOTER_LINKS = { - Product: [ - { label: "Pricing", href: "#" }, - { label: "Features", href: "#" }, - { label: "Download", href: "#" }, - ], - Company: [ - { label: "About", href: "#" }, - { label: "Careers", href: "#" }, - { label: "Contact", href: "#" }, - ], - Resources: [ - { label: "Documentation", href: "#" }, - { label: "Support", href: "#" }, - { label: "Privacy", href: "#" }, - ], - Connect: [ - { label: "GitHub", href: "#" }, - { label: "Twitter", href: "#" }, - { label: "Discord", href: "#" }, - ], -} as const; - -export function Footer() { - return ( -
-
- {/* Main footer content */} -
- {Object.entries(FOOTER_LINKS).map(([category, links], idx) => ( - -

- {category} -

- -
- ))} -
- - {/* Bottom section */} - -
- โЇ - Superset -
-

- ยฉ {new Date().getFullYear()} Superset. All rights reserved. -

-
-
-
- ); -} diff --git a/apps/blog/src/components/Header.tsx b/apps/blog/src/components/Header.tsx deleted file mode 100644 index cad5b01d36b..00000000000 --- a/apps/blog/src/components/Header.tsx +++ /dev/null @@ -1,142 +0,0 @@ -"use client"; - -import { motion } from "framer-motion"; -import Link from "next/link"; -import { useState } from "react"; - -const NAV_LINKS = [ - { label: "Features", href: "#" }, - { label: "Product", href: "#" }, - { label: "Company", href: "#" }, - { label: "Resources", href: "#" }, -] as const; - -export function Header() { - const [isMenuOpen, setIsMenuOpen] = useState(false); - - return ( -
- -
- ); -} diff --git a/apps/blog/src/components/motion/FadeUp.tsx b/apps/blog/src/components/motion/FadeUp.tsx deleted file mode 100644 index 652d2d795b4..00000000000 --- a/apps/blog/src/components/motion/FadeUp.tsx +++ /dev/null @@ -1,46 +0,0 @@ -"use client"; - -import { motion, type Variants } from "framer-motion"; -import type { ReactNode } from "react"; - -interface FadeUpProps { - children: ReactNode; - delay?: number; - duration?: number; - className?: string; -} - -const fadeUpVariants: Variants = { - hidden: { - opacity: 0, - y: 24, - }, - visible: { - opacity: 1, - y: 0, - }, -}; - -export function FadeUp({ - children, - delay = 0, - duration = 0.5, - className, -}: FadeUpProps) { - return ( - - {children} - - ); -} diff --git a/apps/blog/src/components/motion/HeroParallax.tsx b/apps/blog/src/components/motion/HeroParallax.tsx deleted file mode 100644 index d2c911a85a1..00000000000 --- a/apps/blog/src/components/motion/HeroParallax.tsx +++ /dev/null @@ -1,63 +0,0 @@ -"use client"; - -import { motion, useScroll, useTransform } from "framer-motion"; -import type { ReactNode } from "react"; -import { createContext, useContext, useEffect, useRef, useState } from "react"; - -interface HeroParallaxProps { - children: ReactNode; - speed?: number; - className?: string; -} - -// Context to share visibility state with Three.js canvas -const HeroVisibilityContext = createContext(true); - -export function useHeroVisibility() { - return useContext(HeroVisibilityContext); -} - -export function HeroParallax({ children, className }: HeroParallaxProps) { - const ref = useRef(null); - const [isVisible, setIsVisible] = useState(true); - - const { scrollYProgress } = useScroll({ - target: ref, - offset: ["start start", "end start"], - }); - - const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [1, 0.5, 0]); - - // Track visibility with Intersection Observer - useEffect(() => { - const element = ref.current; - if (!element) return; - - const observer = new IntersectionObserver( - (entries) => { - const entry = entries[0]; - if (entry) { - setIsVisible(entry.isIntersecting); - } - }, - { - threshold: 0, - rootMargin: "100px", // Start rendering slightly before entering viewport - }, - ); - - observer.observe(element); - - return () => { - observer.disconnect(); - }; - }, []); - - return ( - -
- {children} -
-
- ); -} diff --git a/apps/blog/src/components/motion/TiltCard.tsx b/apps/blog/src/components/motion/TiltCard.tsx deleted file mode 100644 index b42cb491917..00000000000 --- a/apps/blog/src/components/motion/TiltCard.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client"; - -import { motion, useMotionTemplate, useMotionValue } from "framer-motion"; -import type { ReactNode } from "react"; - -interface TiltCardProps { - children: ReactNode; - className?: string; -} - -export function TiltCard({ children, className }: TiltCardProps) { - const mouseX = useMotionValue(0); - const mouseY = useMotionValue(0); - - const rotateX = useMotionTemplate`${mouseY}deg`; - const rotateY = useMotionTemplate`${mouseX}deg`; - - function handleMouseMove(event: React.MouseEvent) { - const rect = event.currentTarget.getBoundingClientRect(); - const width = rect.width; - const height = rect.height; - const mouseXPos = event.clientX - rect.left; - const mouseYPos = event.clientY - rect.top; - - const xPct = mouseXPos / width - 0.5; - const yPct = mouseYPos / height - 0.5; - - mouseX.set(xPct * 20); - mouseY.set(yPct * -20); - } - - function handleMouseLeave() { - mouseX.set(0); - mouseY.set(0); - } - - return ( - -
{children}
-
- ); -} diff --git a/apps/blog/src/components/motion/index.ts b/apps/blog/src/components/motion/index.ts deleted file mode 100644 index c6a56bb63bf..00000000000 --- a/apps/blog/src/components/motion/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { FadeUp } from "./FadeUp"; -export { HeroParallax } from "./HeroParallax"; -export { TiltCard } from "./TiltCard"; diff --git a/apps/blog/src/components/three/HeroCanvas.tsx b/apps/blog/src/components/three/HeroCanvas.tsx deleted file mode 100644 index 640ab7afea8..00000000000 --- a/apps/blog/src/components/three/HeroCanvas.tsx +++ /dev/null @@ -1,327 +0,0 @@ -"use client"; - -import { Text } from "@react-three/drei"; -import { Canvas, useFrame, useThree } from "@react-three/fiber"; -import { Suspense, useMemo, useRef } from "react"; -import type { Mesh, PointLight } from "three"; -import * as THREE from "three"; -import { useHeroVisibility } from "../motion/HeroParallax"; - -// Configuration constants -const LIGHT_CONFIG = { - INTENSITY: 25, - Z_POSITION: 2, - DEFAULT_X_RATIO: 0.18, - DEFAULT_Y_RATIO: 0.01, - HUE_MIN: 180, - HUE_MAX: 270, - SATURATION_MIN: 60, - SATURATION_MAX: 100, - LIGHTNESS: 65, -} as const; - -const MATERIAL_CONFIG = { - BASE_COLOR: "#1a1a1a", - ROUGHNESS: 0.8, - METALNESS: 0.2, - AMBIENT_INTENSITY: 0.95, - DIFFUSE_INTENSITY: 0.15, - SPECULAR_INTENSITY: 0.05, - ATTENUATION_LINEAR: 0.1, - ATTENUATION_QUADRATIC: 0.05, -} as const; - -const TEXT_CONFIG = { - POSITION: [0, 0.5, 1] as [number, number, number], - FONT_SIZE: 1.8, - FONT_SIZE_OUTER: 1.805, - LAYER_COUNT: 15, - LAYER_SPACING: 0.05, - COLOR: "#2c3539", - METALNESS: 0.85, - ROUGHNESS: 0.25, -} as const; - -const GEOMETRY_CONFIG = { - PLANE_SIZE_MULTIPLIER: 1.5, - PLANE_SEGMENTS: 40, -} as const; - -// Custom shader for GPU-accelerated wave animation -const waveVertexShader = ` - uniform float uTime; - varying vec3 vNormal; - varying vec3 vPosition; - - void main() { - vec3 pos = position; - - // Create wave effect using sine waves - float wave1 = sin(pos.x * 0.5 + uTime * 0.5) * 0.1; - float wave2 = sin(pos.y * 0.5 + uTime * 0.3) * 0.1; - pos.z += wave1 + wave2; - - // Calculate normal based on wave derivatives for proper lighting - float dx = cos(pos.x * 0.5 + uTime * 0.5) * 0.05; - float dy = cos(pos.y * 0.5 + uTime * 0.3) * 0.05; - vec3 computedNormal = normalize(vec3(-dx, -dy, 1.0)); - - vNormal = normalize(normalMatrix * computedNormal); - vPosition = (modelViewMatrix * vec4(pos, 1.0)).xyz; - gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); - } -`; - -const waveFragmentShader = ` - uniform vec3 uColor; - uniform float uRoughness; - uniform float uMetalness; - uniform vec3 uLightPosition; - uniform vec3 uLightColor; - uniform float uLightIntensity; - varying vec3 vNormal; - varying vec3 vPosition; - - void main() { - // Calculate light direction and distance - vec3 lightDir = uLightPosition - vPosition; - float distance = length(lightDir); - lightDir = normalize(lightDir); - - // Attenuation (light falloff) - much stronger falloff - float attenuation = uLightIntensity / (1.0 + 0.1 * distance + 0.05 * distance * distance); - - // Diffuse lighting - very subtle - float diff = max(dot(vNormal, lightDir), 0.0); - - // Specular lighting - very subtle - vec3 viewDir = normalize(-vPosition); - vec3 halfDir = normalize(lightDir + viewDir); - float spec = pow(max(dot(vNormal, halfDir), 0.0), 32.0); - - // Combine lighting - much more subtle effect - vec3 ambient = uColor * 0.95; // Mostly base color - vec3 diffuse = uColor * diff * uLightColor * attenuation * 0.15; // Very subtle diffuse - vec3 specular = uLightColor * spec * attenuation * 0.05 * (1.0 - uRoughness); // Very subtle specular - - vec3 finalColor = ambient + diffuse + specular; - gl_FragColor = vec4(finalColor, 1.0); - } -`; - -function LitBackground() { - const meshRef = useRef(null); - const lightRef = useRef(null); - const textGroupRef = useRef(null); - const { viewport, camera } = useThree(); - const isVisible = useHeroVisibility(); - - // Create shader material with uniforms - const shaderMaterial = useMemo( - () => ({ - uniforms: { - uTime: { value: 0 }, - uColor: { value: new THREE.Color(MATERIAL_CONFIG.BASE_COLOR) }, - uRoughness: { value: MATERIAL_CONFIG.ROUGHNESS }, - uMetalness: { value: MATERIAL_CONFIG.METALNESS }, - uLightPosition: { - value: new THREE.Vector3(0, 0, LIGHT_CONFIG.Z_POSITION), - }, - uLightColor: { value: new THREE.Color("#ffffff") }, - uLightIntensity: { value: LIGHT_CONFIG.INTENSITY }, - }, - vertexShader: waveVertexShader, - fragmentShader: waveFragmentShader, - }), - [], - ); - - useFrame((state) => { - // Skip expensive operations when hero is not visible - if (!isVisible) return; - - // Check if mouse has moved (not at default 0,0) - const hasMouseMoved = state.mouse.x !== 0 || state.mouse.y !== 0; - - // Convert normalized mouse coords to full viewport range, or use default position - const x = hasMouseMoved - ? state.mouse.x * viewport.width - : viewport.width * LIGHT_CONFIG.DEFAULT_X_RATIO; - const y = hasMouseMoved - ? state.mouse.y * viewport.height - : viewport.height * LIGHT_CONFIG.DEFAULT_Y_RATIO; - - // Change color based on position - cooler palette (blue to cyan to purple) - const hue = - LIGHT_CONFIG.HUE_MIN + - ((state.mouse.x + 1) / 2) * (LIGHT_CONFIG.HUE_MAX - LIGHT_CONFIG.HUE_MIN); - const saturation = - LIGHT_CONFIG.SATURATION_MIN + - ((state.mouse.y + 1) / 2) * - (LIGHT_CONFIG.SATURATION_MAX - LIGHT_CONFIG.SATURATION_MIN); - const lightness = LIGHT_CONFIG.LIGHTNESS; - - if (lightRef.current) { - // Position light slightly in front of the plane - lightRef.current.position.set(x, y, LIGHT_CONFIG.Z_POSITION); - lightRef.current.color.setHSL( - hue / 360, - saturation / 100, - lightness / 100, - ); - } - - // Make the text group face the camera - if (textGroupRef.current) { - textGroupRef.current.lookAt(camera.position); - } - - // Make the plane always face the camera and update shader uniforms - if (meshRef.current) { - meshRef.current.lookAt(camera.position); - - // Update shader uniforms (GPU handles the animation and lighting) - const material = meshRef.current.material as THREE.ShaderMaterial; - if (material.uniforms) { - if (material.uniforms.uTime) { - material.uniforms.uTime.value = state.clock.elapsedTime; - } - // Update light position and color in shader - if (material.uniforms.uLightPosition) { - material.uniforms.uLightPosition.value.set( - x, - y, - LIGHT_CONFIG.Z_POSITION, - ); - } - if (material.uniforms.uLightColor) { - material.uniforms.uLightColor.value.setHSL( - hue / 360, - saturation / 100, - lightness / 100, - ); - } - } - } - }); - - return ( - <> - {/* Background plane that fills the viewport and faces camera */} - - - - - - {/* 3D Text that reacts to light */} - - {/* Outer edge layer - highly metallic */} - - โЇ - - - - {/* Create depth by layering multiple text instances - reduced from 30 to 15 for performance */} - {Array.from({ length: TEXT_CONFIG.LAYER_COUNT }, (_, i) => ( - - โЇ - - - ))} - - - {/* Ambient light for base visibility */} - - - {/* Static directional lights for consistent highlights */} - - - - {/* Point light that follows mouse */} - - - ); -} - -interface HeroCanvasProps { - className?: string; -} - -export function HeroCanvas({ className }: HeroCanvasProps) { - return ( -
- - - - - -
- ); -} diff --git a/apps/blog/src/components/three/index.ts b/apps/blog/src/components/three/index.ts deleted file mode 100644 index dc3fd4af5fd..00000000000 --- a/apps/blog/src/components/three/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { HeroCanvas } from "./HeroCanvas"; diff --git a/apps/blog/tsconfig.json b/apps/blog/tsconfig.json deleted file mode 100644 index 600980abb48..00000000000 --- a/apps/blog/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "@superset/typescript/next.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/apps/docs/package.json b/apps/docs/package.json index 7e65907d9fb..7b17da44ed8 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "clean": "git clean -xdf .cache .next .turbo node_modules", - "dev": "next dev --port 3001", + "dev": "next dev --port 3004", "build": "next build", "start": "next start", "typecheck": "tsc --noEmit" diff --git a/apps/docs/public/favicon.ico b/apps/docs/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9c3517c0d4eb3ac1774dc5ea52e63d5ae7f8601e GIT binary patch literal 41839 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 z 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 literal 0 HcmV?d00001 diff --git a/apps/marketing/next-env.d.ts b/apps/marketing/next-env.d.ts new file mode 100644 index 00000000000..830fb594ca2 --- /dev/null +++ b/apps/marketing/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/marketing/next.config.ts b/apps/marketing/next.config.ts new file mode 100644 index 00000000000..b74ea3041fc --- /dev/null +++ b/apps/marketing/next.config.ts @@ -0,0 +1,11 @@ +import type { NextConfig } from "next"; + +const config: NextConfig = { + reactStrictMode: true, + experimental: { + reactCompiler: true, + }, + typescript: { ignoreBuildErrors: true }, +}; + +export default config; diff --git a/apps/marketing/package.json b/apps/marketing/package.json new file mode 100644 index 00000000000..c3b016bb93a --- /dev/null +++ b/apps/marketing/package.json @@ -0,0 +1,42 @@ +{ + "name": "@superset/marketing", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "git clean -xdf .cache .next .turbo node_modules", + "dev": "next dev --port 3002", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@react-three/drei": "^10.7.6", + "@react-three/fiber": "^9.4.0", + "@superset/ui": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "framer-motion": "^12.23.24", + "geist": "^1.5.1", + "lucide-react": "^0.560.0", + "next": "^15.5.7", + "next-themes": "^0.4.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-fast-marquee": "^1.6.5", + "react-icons": "^5.5.0", + "three": "^0.181.2", + "zod": "^4.1.13" + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@tailwindcss/postcss": "^4.0.9", + "@types/mdx": "^2.0.13", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "@types/three": "^0.181.0", + "babel-plugin-react-compiler": "^1.0.0", + "tailwindcss": "^4.0.9", + "typescript": "^5.9.3" + } +} diff --git a/apps/marketing/postcss.config.mjs b/apps/marketing/postcss.config.mjs new file mode 100644 index 00000000000..c2ddf748220 --- /dev/null +++ b/apps/marketing/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/apps/marketing/public/favicon.ico b/apps/marketing/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9c3517c0d4eb3ac1774dc5ea52e63d5ae7f8601e GIT binary patch literal 41839 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 z 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 literal 0 HcmV?d00001 diff --git a/apps/website/public/hero/change-themes.gif b/apps/marketing/public/hero/change-themes.gif similarity index 100% rename from apps/website/public/hero/change-themes.gif rename to apps/marketing/public/hero/change-themes.gif diff --git a/apps/website/public/hero/manage-terminals.gif b/apps/marketing/public/hero/manage-terminals.gif similarity index 100% rename from apps/website/public/hero/manage-terminals.gif rename to apps/marketing/public/hero/manage-terminals.gif diff --git a/apps/website/public/hero/open-worktrees.gif b/apps/marketing/public/hero/open-worktrees.gif similarity index 100% rename from apps/website/public/hero/open-worktrees.gif rename to apps/marketing/public/hero/open-worktrees.gif diff --git a/apps/website/public/hero/use-agents.gif b/apps/marketing/public/hero/use-agents.gif similarity index 100% rename from apps/website/public/hero/use-agents.gif rename to apps/marketing/public/hero/use-agents.gif diff --git a/apps/website/public/title.svg b/apps/marketing/public/title.svg similarity index 100% rename from apps/website/public/title.svg rename to apps/marketing/public/title.svg diff --git a/apps/website/src/app/components/CTASection/CTASection.tsx b/apps/marketing/src/app/components/CTASection/CTASection.tsx similarity index 100% rename from apps/website/src/app/components/CTASection/CTASection.tsx rename to apps/marketing/src/app/components/CTASection/CTASection.tsx diff --git a/apps/website/src/app/components/CTASection/index.ts b/apps/marketing/src/app/components/CTASection/index.ts similarity index 100% rename from apps/website/src/app/components/CTASection/index.ts rename to apps/marketing/src/app/components/CTASection/index.ts diff --git a/apps/website/src/app/components/ClientLogosSection/ClientLogosSection.tsx b/apps/marketing/src/app/components/ClientLogosSection/ClientLogosSection.tsx similarity index 100% rename from apps/website/src/app/components/ClientLogosSection/ClientLogosSection.tsx rename to apps/marketing/src/app/components/ClientLogosSection/ClientLogosSection.tsx diff --git a/apps/website/src/app/components/ClientLogosSection/constants.ts b/apps/marketing/src/app/components/ClientLogosSection/constants.ts similarity index 100% rename from apps/website/src/app/components/ClientLogosSection/constants.ts rename to apps/marketing/src/app/components/ClientLogosSection/constants.ts diff --git a/apps/website/src/app/components/ClientLogosSection/index.ts b/apps/marketing/src/app/components/ClientLogosSection/index.ts similarity index 100% rename from apps/website/src/app/components/ClientLogosSection/index.ts rename to apps/marketing/src/app/components/ClientLogosSection/index.ts diff --git a/apps/website/src/app/components/DownloadButton/DownloadButton.tsx b/apps/marketing/src/app/components/DownloadButton/DownloadButton.tsx similarity index 100% rename from apps/website/src/app/components/DownloadButton/DownloadButton.tsx rename to apps/marketing/src/app/components/DownloadButton/DownloadButton.tsx diff --git a/apps/website/src/app/components/DownloadButton/index.ts b/apps/marketing/src/app/components/DownloadButton/index.ts similarity index 100% rename from apps/website/src/app/components/DownloadButton/index.ts rename to apps/marketing/src/app/components/DownloadButton/index.ts diff --git a/apps/website/src/app/components/Footer/Footer.tsx b/apps/marketing/src/app/components/Footer/Footer.tsx similarity index 100% rename from apps/website/src/app/components/Footer/Footer.tsx rename to apps/marketing/src/app/components/Footer/Footer.tsx diff --git a/apps/website/src/app/components/Footer/index.ts b/apps/marketing/src/app/components/Footer/index.ts similarity index 100% rename from apps/website/src/app/components/Footer/index.ts rename to apps/marketing/src/app/components/Footer/index.ts diff --git a/apps/website/src/app/components/Header/Header.tsx b/apps/marketing/src/app/components/Header/Header.tsx similarity index 100% rename from apps/website/src/app/components/Header/Header.tsx rename to apps/marketing/src/app/components/Header/Header.tsx diff --git a/apps/website/src/app/components/Header/index.ts b/apps/marketing/src/app/components/Header/index.ts similarity index 100% rename from apps/website/src/app/components/Header/index.ts rename to apps/marketing/src/app/components/Header/index.ts diff --git a/apps/website/src/app/components/HeroSection/HeroSection.tsx b/apps/marketing/src/app/components/HeroSection/HeroSection.tsx similarity index 100% rename from apps/website/src/app/components/HeroSection/HeroSection.tsx rename to apps/marketing/src/app/components/HeroSection/HeroSection.tsx diff --git a/apps/website/src/app/components/HeroSection/index.ts b/apps/marketing/src/app/components/HeroSection/index.ts similarity index 100% rename from apps/website/src/app/components/HeroSection/index.ts rename to apps/marketing/src/app/components/HeroSection/index.ts diff --git a/apps/website/src/app/components/JoinWaitlistButton/JoinWaitlistButton.tsx b/apps/marketing/src/app/components/JoinWaitlistButton/JoinWaitlistButton.tsx similarity index 100% rename from apps/website/src/app/components/JoinWaitlistButton/JoinWaitlistButton.tsx rename to apps/marketing/src/app/components/JoinWaitlistButton/JoinWaitlistButton.tsx diff --git a/apps/website/src/app/components/JoinWaitlistButton/index.ts b/apps/marketing/src/app/components/JoinWaitlistButton/index.ts similarity index 100% rename from apps/website/src/app/components/JoinWaitlistButton/index.ts rename to apps/marketing/src/app/components/JoinWaitlistButton/index.ts diff --git a/apps/website/src/app/components/PlatformDropdown/PlatformDropdown.tsx b/apps/marketing/src/app/components/PlatformDropdown/PlatformDropdown.tsx similarity index 100% rename from apps/website/src/app/components/PlatformDropdown/PlatformDropdown.tsx rename to apps/marketing/src/app/components/PlatformDropdown/PlatformDropdown.tsx diff --git a/apps/website/src/app/components/PlatformDropdown/index.ts b/apps/marketing/src/app/components/PlatformDropdown/index.ts similarity index 100% rename from apps/website/src/app/components/PlatformDropdown/index.ts rename to apps/marketing/src/app/components/PlatformDropdown/index.ts diff --git a/apps/website/src/app/components/SecuritySection/SecuritySection.tsx b/apps/marketing/src/app/components/SecuritySection/SecuritySection.tsx similarity index 100% rename from apps/website/src/app/components/SecuritySection/SecuritySection.tsx rename to apps/marketing/src/app/components/SecuritySection/SecuritySection.tsx diff --git a/apps/website/src/app/components/SecuritySection/index.ts b/apps/marketing/src/app/components/SecuritySection/index.ts similarity index 100% rename from apps/website/src/app/components/SecuritySection/index.ts rename to apps/marketing/src/app/components/SecuritySection/index.ts diff --git a/apps/website/src/app/components/SocialLinks/SocialLinks.tsx b/apps/marketing/src/app/components/SocialLinks/SocialLinks.tsx similarity index 100% rename from apps/website/src/app/components/SocialLinks/SocialLinks.tsx rename to apps/marketing/src/app/components/SocialLinks/SocialLinks.tsx diff --git a/apps/website/src/app/components/SocialLinks/index.ts b/apps/marketing/src/app/components/SocialLinks/index.ts similarity index 100% rename from apps/website/src/app/components/SocialLinks/index.ts rename to apps/marketing/src/app/components/SocialLinks/index.ts diff --git a/apps/website/src/app/components/TrustedBySection/TrustedBySection.tsx b/apps/marketing/src/app/components/TrustedBySection/TrustedBySection.tsx similarity index 100% rename from apps/website/src/app/components/TrustedBySection/TrustedBySection.tsx rename to apps/marketing/src/app/components/TrustedBySection/TrustedBySection.tsx diff --git a/apps/website/src/app/components/TrustedBySection/index.ts b/apps/marketing/src/app/components/TrustedBySection/index.ts similarity index 100% rename from apps/website/src/app/components/TrustedBySection/index.ts rename to apps/marketing/src/app/components/TrustedBySection/index.ts diff --git a/apps/website/src/app/components/VideoSection/VideoSection.tsx b/apps/marketing/src/app/components/VideoSection/VideoSection.tsx similarity index 100% rename from apps/website/src/app/components/VideoSection/VideoSection.tsx rename to apps/marketing/src/app/components/VideoSection/VideoSection.tsx diff --git a/apps/website/src/app/components/VideoSection/index.ts b/apps/marketing/src/app/components/VideoSection/index.ts similarity index 100% rename from apps/website/src/app/components/VideoSection/index.ts rename to apps/marketing/src/app/components/VideoSection/index.ts diff --git a/apps/website/src/app/components/WaitlistModal/WaitlistModal.tsx b/apps/marketing/src/app/components/WaitlistModal/WaitlistModal.tsx similarity index 100% rename from apps/website/src/app/components/WaitlistModal/WaitlistModal.tsx rename to apps/marketing/src/app/components/WaitlistModal/WaitlistModal.tsx diff --git a/apps/website/src/app/components/WaitlistModal/index.ts b/apps/marketing/src/app/components/WaitlistModal/index.ts similarity index 100% rename from apps/website/src/app/components/WaitlistModal/index.ts rename to apps/marketing/src/app/components/WaitlistModal/index.ts diff --git a/apps/website/src/app/globals.css b/apps/marketing/src/app/globals.css similarity index 100% rename from apps/website/src/app/globals.css rename to apps/marketing/src/app/globals.css diff --git a/apps/website/src/app/layout.tsx b/apps/marketing/src/app/layout.tsx similarity index 92% rename from apps/website/src/app/layout.tsx rename to apps/marketing/src/app/layout.tsx index 4f606677b07..ec02542c036 100644 --- a/apps/website/src/app/layout.tsx +++ b/apps/marketing/src/app/layout.tsx @@ -2,7 +2,6 @@ import type { Metadata } from "next"; import { IBM_Plex_Mono, Inter } from "next/font/google"; import Script from "next/script"; import { ThemeProvider } from "next-themes"; -import { TRPCReactProvider } from "@/trpc/react"; import "./globals.css"; const ibmPlexMono = IBM_Plex_Mono({ @@ -42,7 +41,7 @@ export default function RootLayout({ - {children} + {children} diff --git a/apps/website/src/app/page.tsx b/apps/marketing/src/app/page.tsx similarity index 100% rename from apps/website/src/app/page.tsx rename to apps/marketing/src/app/page.tsx diff --git a/apps/website/src/app/privacy-policy/page.tsx b/apps/marketing/src/app/privacy-policy/page.tsx similarity index 100% rename from apps/website/src/app/privacy-policy/page.tsx rename to apps/marketing/src/app/privacy-policy/page.tsx diff --git a/apps/website/src/app/scripts/page.tsx b/apps/marketing/src/app/scripts/page.tsx similarity index 100% rename from apps/website/src/app/scripts/page.tsx rename to apps/marketing/src/app/scripts/page.tsx diff --git a/apps/website/src/app/terms-of-service/page.tsx b/apps/marketing/src/app/terms-of-service/page.tsx similarity index 100% rename from apps/website/src/app/terms-of-service/page.tsx rename to apps/marketing/src/app/terms-of-service/page.tsx diff --git a/apps/website/src/constants.ts b/apps/marketing/src/constants.ts similarity index 100% rename from apps/website/src/constants.ts rename to apps/marketing/src/constants.ts diff --git a/apps/website/src/env.ts b/apps/marketing/src/env.ts similarity index 66% rename from apps/website/src/env.ts rename to apps/marketing/src/env.ts index 4ebd90a0753..d628bc6f5b7 100644 --- a/apps/website/src/env.ts +++ b/apps/marketing/src/env.ts @@ -9,14 +9,13 @@ export const env = createEnv({ .enum(["development", "production", "test"]) .default("development"), }, - server: { - DATABASE_URL: z.url(), + server: {}, + client: { + NEXT_PUBLIC_API_URL: z.string().url().optional(), }, - client: {}, - /** - * Destructure all variables from `process.env` to make sure they aren't tree-shaken away. - */ experimental__runtimeEnv: { NODE_ENV: process.env.NODE_ENV, + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, }, + skipValidation: !!process.env.SKIP_ENV_VALIDATION, }); diff --git a/apps/marketing/tsconfig.json b/apps/marketing/tsconfig.json new file mode 100644 index 00000000000..fec43826096 --- /dev/null +++ b/apps/marketing/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "@superset/typescript/base.json", + "compilerOptions": { + "plugins": [ + { + "name": "next" + } + ], + "module": "ESNext", + "moduleResolution": "Bundler", + "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts new file mode 100644 index 00000000000..830fb594ca2 --- /dev/null +++ b/apps/web/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts new file mode 100644 index 00000000000..c9c33d2ed02 --- /dev/null +++ b/apps/web/next.config.ts @@ -0,0 +1,10 @@ +import type { NextConfig } from "next"; + +const config: NextConfig = { + experimental: { + reactCompiler: true, + }, + typescript: { ignoreBuildErrors: true }, +}; + +export default config; diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 00000000000..56fcfdced87 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,45 @@ +{ + "name": "@superset/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "git clean -xdf .cache .next .turbo node_modules", + "dev": "next dev", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@superset/db": "workspace:*", + "@superset/queries": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", + "@superset/ui": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query-devtools": "^5.90.10", + "@trpc/client": "^11.7.1", + "@trpc/server": "^11.7.1", + "@trpc/tanstack-react-query": "^11.7.1", + "geist": "^1.5.1", + "lucide-react": "^0.560.0", + "next": "^15.5.7", + "next-themes": "^0.4.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "server-only": "^0.0.1", + "superjson": "^2.2.5", + "zod": "^4.1.13" + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@tailwindcss/postcss": "^4.0.9", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "babel-plugin-react-compiler": "^1.0.0", + "tailwindcss": "^4.0.9", + "typescript": "^5.9.3" + } +} diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs new file mode 100644 index 00000000000..c2ddf748220 --- /dev/null +++ b/apps/web/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9c3517c0d4eb3ac1774dc5ea52e63d5ae7f8601e GIT binary patch literal 41839 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 z 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 literal 0 HcmV?d00001 diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css new file mode 100644 index 00000000000..ee297b51232 --- /dev/null +++ b/apps/web/src/app/globals.css @@ -0,0 +1,4 @@ +@import "tailwindcss"; + +@import "@superset/ui/globals.css"; +@source "../../../../packages/ui/src/**/*.{ts,tsx}"; diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 00000000000..909a1b44e7c --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,40 @@ +import { cn } from "@superset/ui/utils"; +import { Toaster } from "@superset/ui/sonner"; +import { GeistMono } from "geist/font/mono"; +import { GeistSans } from "geist/font/sans"; +import type { Metadata, Viewport } from "next"; + +import "./globals.css"; + +import { Providers } from "./providers"; + +export const metadata: Metadata = { + title: "Superset", + description: "Run 10+ parallel coding agents on your machine", +}; + +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + + ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 00000000000..cc8557645e0 --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,60 @@ +import { api } from "@/trpc/server"; + +export default async function Home() { + const trpc = await api(); + + const users = await trpc.user.all.query(); + + let me = null; + let authError = null; + try { + me = await trpc.user.me.query(); + } catch (e) { + authError = e instanceof Error ? e.message : "Unknown error"; + } + + return ( +
+

Superset Web

+

+ Run 10+ parallel coding agents on your machine +

+ +
+
+

Public Query

+

user.all()

+

+ Users in database: {users.length} +

+ {users.length > 0 && ( +
    + {users.slice(0, 3).map((user) => ( +
  • {user.email}
  • + ))} +
+ )} +
+ +
+

Protected Query

+

user.me()

+ {authError ? ( +

+ Error: {authError} +

+ ) : me ? ( +
+

Logged in as: {me.email}

+

{me.name}

+
+ ) : ( +

+ No user found for MOCK_USER_ID +

+ )} +
+
+
+ ); +} diff --git a/apps/web/src/app/providers.tsx b/apps/web/src/app/providers.tsx new file mode 100644 index 00000000000..99deeb2dd13 --- /dev/null +++ b/apps/web/src/app/providers.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { ThemeProvider } from "next-themes"; + +import { TRPCReactProvider } from "../trpc/react"; + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + + ); +} diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts new file mode 100644 index 00000000000..ec854e90682 --- /dev/null +++ b/apps/web/src/env.ts @@ -0,0 +1,32 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { vercel } from "@t3-oss/env-nextjs/presets-zod"; +import { z } from "zod"; + +export const env = createEnv({ + extends: [vercel()], + shared: { + NODE_ENV: z.enum(["development", "production", "test"]).default("development"), + }, + + server: { + // Database (needed by @superset/trpc dependency) + DATABASE_URL: z.string().url(), + DATABASE_URL_UNPOOLED: z.string().url(), + }, + + client: { + NEXT_PUBLIC_API_URL: z.string().url(), + NEXT_PUBLIC_MARKETING_URL: z.string().url().optional(), + NEXT_PUBLIC_DOCS_URL: z.string().url().optional(), + }, + + experimental__runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + NEXT_PUBLIC_MARKETING_URL: process.env.NEXT_PUBLIC_MARKETING_URL, + NEXT_PUBLIC_DOCS_URL: process.env.NEXT_PUBLIC_DOCS_URL, + }, + + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + emptyStringAsUndefined: true, +}); diff --git a/apps/web/src/trpc/query-client.ts b/apps/web/src/trpc/query-client.ts new file mode 100644 index 00000000000..d5e339e9d20 --- /dev/null +++ b/apps/web/src/trpc/query-client.ts @@ -0,0 +1,29 @@ +import { defaultShouldDehydrateQuery, QueryClient } from "@tanstack/react-query"; +import SuperJSON from "superjson"; + +export const createQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + // With SSR, we usually want to set some default staleTime + // above 0 to avoid refetching immediately on the client + staleTime: 30 * 1000, + }, + dehydrate: { + serializeData: SuperJSON.serialize, + shouldDehydrateQuery: (query) => + defaultShouldDehydrateQuery(query) || query.state.status === "pending", + shouldRedactErrors: () => { + // We should not catch Next.js server errors + // as that's how Next.js detects dynamic pages + // so we cannot redact them. + // Next.js also automatically redacts errors for us + // with better digests. + return false; + }, + }, + hydrate: { + deserializeData: SuperJSON.deserialize, + }, + }, + }); diff --git a/apps/web/src/trpc/react.tsx b/apps/web/src/trpc/react.tsx new file mode 100644 index 00000000000..34f55a4de22 --- /dev/null +++ b/apps/web/src/trpc/react.tsx @@ -0,0 +1,62 @@ +"use client"; + +import type { AppRouter } from "@superset/trpc"; +import type { QueryClient } from "@tanstack/react-query"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { createTRPCClient, httpBatchStreamLink, loggerLink } from "@trpc/client"; +import { createTRPCContext } from "@trpc/tanstack-react-query"; +import { useState } from "react"; +import SuperJSON from "superjson"; + +import { env } from "../env"; +import { createQueryClient } from "./query-client"; + +let clientQueryClientSingleton: QueryClient | undefined; +const getQueryClient = () => { + if (typeof window === "undefined") { + // Server: always make a new query client + return createQueryClient(); + } + // Browser: use singleton pattern to keep the same query client + if (!clientQueryClientSingleton) { + clientQueryClientSingleton = createQueryClient(); + } + return clientQueryClientSingleton; +}; + +const context = createTRPCContext(); +export const { useTRPC, TRPCProvider } = context; +export type UseTRPC = typeof useTRPC; + +export function TRPCReactProvider(props: { children: React.ReactNode }) { + const queryClient = getQueryClient(); + + const [trpcClient] = useState(() => + createTRPCClient({ + links: [ + loggerLink({ + enabled: (op) => + env.NODE_ENV === "development" || + (op.direction === "down" && op.result instanceof Error), + }), + httpBatchStreamLink({ + transformer: SuperJSON, + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + const headers = new Headers(); + headers.set("x-trpc-source", "nextjs-react"); + return headers; + }, + }), + ], + }), + ); + + return ( + + + {props.children} + + + ); +} diff --git a/apps/web/src/trpc/server.tsx b/apps/web/src/trpc/server.tsx new file mode 100644 index 00000000000..d842794bc2a --- /dev/null +++ b/apps/web/src/trpc/server.tsx @@ -0,0 +1,26 @@ +import "server-only"; + +import type { AppRouter } from "@superset/trpc"; +import { createTRPCClient, httpBatchLink } from "@trpc/client"; +import { headers } from "next/headers"; +import { cache } from "react"; +import SuperJSON from "superjson"; + +import { env } from "../env"; + +export const api = cache(async () => { + const heads = new Headers(await headers()); + heads.set("x-trpc-source", "rsc"); + + return createTRPCClient({ + links: [ + httpBatchLink({ + transformer: SuperJSON, + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + return Object.fromEntries(heads.entries()); + }, + }), + ], + }); +}); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 00000000000..87769fd1895 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@superset/typescript/base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + "plugins": [{ "name": "next" }] + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/website/.gitignore b/apps/website/.gitignore deleted file mode 100644 index 5ef6a520780..00000000000 --- a/apps/website/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/apps/website/next.config.ts b/apps/website/next.config.ts deleted file mode 100644 index 89bfe82d3aa..00000000000 --- a/apps/website/next.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { - reactStrictMode: true, - transpilePackages: ["@superset/ui"], -}; - -export default nextConfig; diff --git a/apps/website/package.json b/apps/website/package.json deleted file mode 100644 index 65071371dcb..00000000000 --- a/apps/website/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@superset/website", - "version": "0.1.0", - "private": true, - "scripts": { - "clean": "git clean -xdf .cache .next .turbo node_modules", - "dev": "next dev", - "build": "next build", - "start": "next start", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@react-three/drei": "^10.7.6", - "@react-three/fiber": "^9.4.0", - "@superset/trpc": "workspace:*", - "@superset/ui": "workspace:*", - "@t3-oss/env-nextjs": "^0.13.8", - "@tanstack/react-query": "^5.90.10", - "@trpc/client": "^11.7.1", - "@trpc/server": "^11.7.1", - "@trpc/tanstack-react-query": "^11.7.1", - "framer-motion": "^12.23.24", - "geist": "^1.5.1", - "next": "^15.5.7", - "next-themes": "^0.4.6", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-fast-marquee": "^1.6.5", - "react-icons": "^5.5.0", - "superjson": "^2.2.5", - "three": "^0.181.2", - "zod": "^4.1.13" - }, - "devDependencies": { - "@superset/typescript": "workspace:*", - "@tailwindcss/postcss": "^4.0.9", - "@types/mdx": "^2.0.13", - "@types/node": "^24.9.1", - "@types/react": "^19.1.11", - "@types/react-dom": "^19.1.7", - "@types/three": "^0.181.0", - "tailwindcss": "^4.0.9", - "typescript": "^5.9.3" - } -} diff --git a/apps/website/postcss.config.mjs b/apps/website/postcss.config.mjs deleted file mode 100644 index c42f31cab8a..00000000000 --- a/apps/website/postcss.config.mjs +++ /dev/null @@ -1,7 +0,0 @@ -const config = { - plugins: { - "@tailwindcss/postcss": {}, - }, -}; - -export default config; diff --git a/apps/website/src/app/api/trpc/[trpc]/route.ts b/apps/website/src/app/api/trpc/[trpc]/route.ts deleted file mode 100644 index 6dbdfbfaf12..00000000000 --- a/apps/website/src/app/api/trpc/[trpc]/route.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { appRouter, createTRPCContext } from "@superset/trpc"; -import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; -import type { NextRequest } from "next/server"; - -const setCorsHeaders = (res: Response) => { - res.headers.set("Access-Control-Allow-Origin", "*"); - res.headers.set("Access-Control-Request-Method", "*"); - res.headers.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST"); - res.headers.set("Access-Control-Allow-Headers", "*"); - return res; -}; - -export const OPTIONS = () => { - return setCorsHeaders(new Response(null, { status: 204 })); -}; - -const handler = async (req: NextRequest) => { - const response = await fetchRequestHandler({ - endpoint: "/api/trpc", - router: appRouter, - req, - createContext: () => - createTRPCContext({ - headers: req.headers, - }), - onError({ error, path }) { - console.error(`[TRPC Error] ${path ?? ""}:`, error); - }, - }); - - return setCorsHeaders(response); -}; - -export { handler as GET, handler as POST }; diff --git a/apps/website/src/trpc/query-client.ts b/apps/website/src/trpc/query-client.ts deleted file mode 100644 index bc21bfbd493..00000000000 --- a/apps/website/src/trpc/query-client.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - defaultShouldDehydrateQuery, - QueryClient, -} from "@tanstack/react-query"; -import SuperJSON from "superjson"; - -export const createQueryClient = () => - new QueryClient({ - defaultOptions: { - queries: { - staleTime: 30 * 1000, - }, - dehydrate: { - serializeData: SuperJSON.serialize, - shouldDehydrateQuery: (query) => - defaultShouldDehydrateQuery(query) || - query.state.status === "pending", - shouldRedactErrors: () => false, - }, - hydrate: { - deserializeData: SuperJSON.deserialize, - }, - }, - }); diff --git a/apps/website/src/trpc/react.tsx b/apps/website/src/trpc/react.tsx deleted file mode 100644 index 6613ccc7e83..00000000000 --- a/apps/website/src/trpc/react.tsx +++ /dev/null @@ -1,68 +0,0 @@ -"use client"; - -import type { AppRouter } from "@superset/trpc"; -import type { QueryClient } from "@tanstack/react-query"; -import { QueryClientProvider } from "@tanstack/react-query"; -import { - createTRPCClient, - httpBatchStreamLink, - loggerLink, -} from "@trpc/client"; -import { createTRPCContext } from "@trpc/tanstack-react-query"; -import { useState } from "react"; -import SuperJSON from "superjson"; -import { createQueryClient } from "./query-client"; - -let clientQueryClientSingleton: QueryClient | undefined; -const getQueryClient = () => { - if (typeof window === "undefined") { - // Server: always make a new query client - return createQueryClient(); - } - // Browser: use singleton pattern to keep the same query client - if (!clientQueryClientSingleton) { - clientQueryClientSingleton = createQueryClient(); - } - return clientQueryClientSingleton; -}; - -export const { useTRPC, TRPCProvider } = createTRPCContext(); - -function getBaseUrl() { - if (typeof window !== "undefined") return window.location.origin; - if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; - return `http://localhost:3000`; -} - -export function TRPCReactProvider(props: { children: React.ReactNode }) { - const queryClient = getQueryClient(); - - const [trpcClient] = useState(() => - createTRPCClient({ - links: [ - loggerLink({ - enabled: (op) => - process.env.NODE_ENV === "development" || - (op.direction === "down" && op.result instanceof Error), - }), - httpBatchStreamLink({ - transformer: SuperJSON, - url: `${getBaseUrl()}/api/trpc`, - headers() { - const headers = new Headers(); - headers.set("x-trpc-source", "nextjs-react"); - return headers; - }, - }), - ], - }), - ); - - return ( - - - {props.children} - - - ); -} diff --git a/apps/website/src/trpc/server.tsx b/apps/website/src/trpc/server.tsx deleted file mode 100644 index c5bb5fecf20..00000000000 --- a/apps/website/src/trpc/server.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import "server-only"; - -import { type AppRouter, appRouter, createTRPCContext } from "@superset/trpc"; -import { dehydrate, HydrationBoundary } from "@tanstack/react-query"; -import type { TRPCQueryOptions } from "@trpc/tanstack-react-query"; -import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query"; -import { headers } from "next/headers"; -import { cache } from "react"; -import { createQueryClient } from "./query-client"; - -/** - * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when - * handling a tRPC call from a React Server Component. - */ -const createContext = cache(async () => { - const heads = new Headers(await headers()); - heads.set("x-trpc-source", "rsc"); - - return createTRPCContext({ - headers: heads, - }); -}); - -const getQueryClient = cache(createQueryClient); - -export const trpc = createTRPCOptionsProxy({ - router: appRouter, - ctx: createContext, - queryClient: getQueryClient, -}); - -export function HydrateClient(props: { children: React.ReactNode }) { - const queryClient = getQueryClient(); - return ( - - {props.children} - - ); -} - -// biome-ignore lint/suspicious/noExplicitAny: TRPCQueryOptions requires any for generic inference -export function prefetch>>( - queryOptions: T, -) { - const queryClient = getQueryClient(); - if (queryOptions.queryKey[1]?.type === "infinite") { - // biome-ignore lint/suspicious/noExplicitAny: prefetchInfiniteQuery type mismatch with TRPCQueryOptions - void queryClient.prefetchInfiniteQuery(queryOptions as any); - } else { - void queryClient.prefetchQuery(queryOptions); - } -} diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json deleted file mode 100644 index 600980abb48..00000000000 --- a/apps/website/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "@superset/typescript/next.json", - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/bun.lock b/bun.lock index 03f4495d80e..116de928bc9 100644 --- a/bun.lock +++ b/bun.lock @@ -10,35 +10,66 @@ "turbo": "^2.5.8", }, }, - "apps/blog": { - "name": "@superset/blog", + "apps/admin": { + "name": "@superset/admin", "version": "0.1.0", "dependencies": { - "@react-three/drei": "^10.7.6", - "@react-three/fiber": "^9.4.0", + "@superset/db": "workspace:*", + "@superset/queries": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", "@superset/ui": "workspace:*", - "framer-motion": "^12.23.24", + "@t3-oss/env-nextjs": "^0.13.8", + "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query-devtools": "^5.90.10", + "@trpc/client": "^11.7.1", + "@trpc/server": "^11.7.1", + "@trpc/tanstack-react-query": "^11.7.1", "geist": "^1.5.1", + "lucide-react": "^0.560.0", "next": "^15.5.7", - "nextra": "^4.6.0", - "nextra-theme-blog": "^4.6.0", - "nextra-theme-docs": "^4.6.0", + "next-themes": "^0.4.6", "react": "^19.1.1", "react-dom": "^19.1.1", - "three": "^0.181.2", + "server-only": "^0.0.1", + "superjson": "^2.2.5", + "zod": "^4.1.13", }, "devDependencies": { "@superset/typescript": "workspace:*", "@tailwindcss/postcss": "^4.0.9", - "@types/mdx": "^2.0.13", "@types/node": "^24.9.1", "@types/react": "^19.1.11", "@types/react-dom": "^19.1.7", - "@types/three": "^0.181.0", + "babel-plugin-react-compiler": "^1.0.0", "tailwindcss": "^4.0.9", "typescript": "^5.9.3", }, }, + "apps/api": { + "name": "@superset/api", + "version": "0.1.0", + "dependencies": { + "@superset/db": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "@trpc/server": "^11.7.1", + "drizzle-orm": "0.45.1", + "next": "^15.5.7", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "zod": "^4.1.13", + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "^5.9.3", + }, + }, "apps/cli": { "name": "@superset/cli", "version": "0.0.0", @@ -195,28 +226,23 @@ "typescript": "^5.9.3", }, }, - "apps/website": { - "name": "@superset/website", + "apps/marketing": { + "name": "@superset/marketing", "version": "0.1.0", "dependencies": { "@react-three/drei": "^10.7.6", "@react-three/fiber": "^9.4.0", - "@superset/trpc": "workspace:*", "@superset/ui": "workspace:*", "@t3-oss/env-nextjs": "^0.13.8", - "@tanstack/react-query": "^5.90.10", - "@trpc/client": "^11.7.1", - "@trpc/server": "^11.7.1", - "@trpc/tanstack-react-query": "^11.7.1", "framer-motion": "^12.23.24", "geist": "^1.5.1", + "lucide-react": "^0.560.0", "next": "^15.5.7", "next-themes": "^0.4.6", "react": "^19.1.1", "react-dom": "^19.1.1", "react-fast-marquee": "^1.6.5", "react-icons": "^5.5.0", - "superjson": "^2.2.5", "three": "^0.181.2", "zod": "^4.1.13", }, @@ -228,6 +254,43 @@ "@types/react": "^19.1.11", "@types/react-dom": "^19.1.7", "@types/three": "^0.181.0", + "babel-plugin-react-compiler": "^1.0.0", + "tailwindcss": "^4.0.9", + "typescript": "^5.9.3", + }, + }, + "apps/web": { + "name": "@superset/web", + "version": "0.1.0", + "dependencies": { + "@superset/db": "workspace:*", + "@superset/queries": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", + "@superset/ui": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query-devtools": "^5.90.10", + "@trpc/client": "^11.7.1", + "@trpc/server": "^11.7.1", + "@trpc/tanstack-react-query": "^11.7.1", + "geist": "^1.5.1", + "lucide-react": "^0.560.0", + "next": "^15.5.7", + "next-themes": "^0.4.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "server-only": "^0.0.1", + "superjson": "^2.2.5", + "zod": "^4.1.13", + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@tailwindcss/postcss": "^4.0.9", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "babel-plugin-react-compiler": "^1.0.0", "tailwindcss": "^4.0.9", "typescript": "^5.9.3", }, @@ -274,7 +337,7 @@ "zod": "^4.1.13", }, "devDependencies": { - "@superset/typescript": "*", + "@superset/typescript": "workspace:*", "typescript": "^5.9.3", }, }, @@ -941,7 +1004,9 @@ "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], - "@superset/blog": ["@superset/blog@workspace:apps/blog"], + "@superset/admin": ["@superset/admin@workspace:apps/admin"], + + "@superset/api": ["@superset/api@workspace:apps/api"], "@superset/cli": ["@superset/cli@workspace:apps/cli"], @@ -951,6 +1016,8 @@ "@superset/docs": ["@superset/docs@workspace:apps/docs"], + "@superset/marketing": ["@superset/marketing@workspace:apps/marketing"], + "@superset/queries": ["@superset/queries@workspace:packages/queries"], "@superset/shared": ["@superset/shared@workspace:packages/shared"], @@ -961,7 +1028,7 @@ "@superset/ui": ["@superset/ui@workspace:packages/ui"], - "@superset/website": ["@superset/website@workspace:apps/website"], + "@superset/web": ["@superset/web@workspace:apps/web"], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -1005,8 +1072,12 @@ "@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="], + "@tanstack/query-devtools": ["@tanstack/query-devtools@5.91.1", "", {}, "sha512-l8bxjk6BMsCaVQH6NzQEE/bEgFy1hAs5qbgXl0xhzezlaQbPk6Mgz9BqEg2vTLPOHD8N4k+w/gdgCbEzecGyNg=="], + "@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="], + "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.91.1", "", { "dependencies": { "@tanstack/query-devtools": "5.91.1" }, "peerDependencies": { "@tanstack/react-query": "^5.90.10", "react": "^18 || ^19" } }, "sha512-tRnJYwEbH0kAOuToy8Ew7bJw1lX3AjkkgSlf/vzb+NpnqmHPdWM+lA2DSdGQSLi1SU0PDRrrCI1vnZnci96CsQ=="], + "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.13", "", { "dependencies": { "@tanstack/virtual-core": "3.13.13" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-4o6oPMDvQv+9gMi8rE6gWmsOjtUZUYIJHv7EB+GblyYdi8U6OqLl8rhHWIUZSL1dUU2dPwTdTgybCKf9EjIrQg=="], "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.13", "", {}, "sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA=="], @@ -1309,6 +1380,8 @@ "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], + "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="], + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], @@ -1667,7 +1740,7 @@ "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], - "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], @@ -2541,7 +2614,7 @@ "react-mosaic-component": ["react-mosaic-component@6.1.1", "", { "dependencies": { "classnames": "^2.3.2", "immutability-helper": "^3.1.1", "lodash": "^4.17.21", "prop-types": "^15.8.1", "rdndmb-html5-to-touch": "^8.0.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dnd-multi-backend": "^8.0.0", "react-dnd-touch-backend": "^16.0.1", "uuid": "^9.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-Ivuj6AxRDlo/H8OiEDU1mdgivxuKbwGOa5Ub6Yf+bHcu0JWioT7ttlpCWF63/gKrJBlRMB6fW9/eNOXINg9+Gg=="], - "react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="], + "react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], @@ -2693,7 +2766,7 @@ "sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], - "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "scroll-into-view-if-needed": ["scroll-into-view-if-needed@3.1.0", "", { "dependencies": { "compute-scroll-into-view": "^3.0.2" } }, "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ=="], @@ -3171,6 +3244,10 @@ "@react-three/drei/cross-env": ["cross-env@7.0.3", "", { "dependencies": { "cross-spawn": "^7.0.1" }, "bin": { "cross-env": "src/bin/cross-env.js", "cross-env-shell": "src/bin/cross-env-shell.js" } }, "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw=="], + "@react-three/fiber/react-reconciler": ["react-reconciler@0.31.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ=="], + + "@react-three/fiber/scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], @@ -3183,8 +3260,6 @@ "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@vue/compiler-core/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - "@vue/compiler-core/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@xyflow/react/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], @@ -3281,8 +3356,6 @@ "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], - "ink/react-reconciler": ["react-reconciler@0.33.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="], - "its-fine/@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="], "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], @@ -3353,6 +3426,8 @@ "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], @@ -3373,8 +3448,6 @@ "react-devtools-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], - "react-dom/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "react-router/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "read-pkg/normalize-package-data": ["normalize-package-data@3.0.3", "", { "dependencies": { "hosted-git-info": "^4.0.1", "is-core-module": "^2.5.0", "semver": "^7.3.4", "validate-npm-package-license": "^3.0.1" } }, "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA=="], @@ -3569,8 +3642,6 @@ "iconv-corefoundation/cli-truncate/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "ink/react-reconciler/scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "launch-ide/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "log-symbols/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], diff --git a/packages/trpc/src/router/user.ts b/packages/trpc/src/router/user.ts index 1ea87c25727..5d1d6cafadd 100644 --- a/packages/trpc/src/router/user.ts +++ b/packages/trpc/src/router/user.ts @@ -3,9 +3,15 @@ import { users } from "@superset/db/schema"; import type { TRPCRouterRecord } from "@trpc/server"; import { desc, eq } from "drizzle-orm"; import { z } from "zod"; -import { publicProcedure } from "../trpc"; +import { protectedProcedure, publicProcedure } from "../trpc"; export const userRouter = { + me: protectedProcedure.query(async ({ ctx }) => { + return db.query.users.findFirst({ + where: eq(users.id, ctx.session.user.id), + }); + }), + all: publicProcedure.query(() => { return db.query.users.findMany({ orderBy: desc(users.createdAt), diff --git a/turbo.jsonc b/turbo.jsonc index 8b3f597d112..f204afda77a 100644 --- a/turbo.jsonc +++ b/turbo.jsonc @@ -4,22 +4,12 @@ "globalEnv": [ "DATABASE_URL", "DATABASE_URL_UNPOOLED", - "E2B_API_KEY", - "OPENROUTER_API_KEY", - "LIVEBLOCKS_SECRET_KEY", - "NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY", - "LANGFUSE_HOST", - "LANGFUSE_PUBLIC_KEY", - "LANGFUSE_SECRET_KEY", - "BLOB_READ_WRITE_TOKEN", - "USER_ID", - "NEXT_PUBLIC_POSTHOG_KEY", - "NEXT_PUBLIC_POSTHOG_HOST", - "NEXT_PUBLIC_SENTRY_DSN", + "MOCK_USER_ID", "NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_WEB_URL", - "SENTRY_AUTH_TOKEN", - "SENTRY_ENVIRONMENT" + "NEXT_PUBLIC_MARKETING_URL", + "NEXT_PUBLIC_DOCS_URL", + "NEXT_PUBLIC_ADMIN_URL" ], "globalPassThroughEnv": [ "NODE_ENV", From 694147748bd2c087baa363715351c27261d263b2 Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Thu, 11 Dec 2025 23:40:43 -0500 Subject: [PATCH 2/5] WIP --- apps/admin/next.config.ts | 8 +- apps/admin/package.json | 86 +++++++++---------- apps/admin/postcss.config.mjs | 6 +- apps/admin/src/app/layout.tsx | 52 +++++------ apps/admin/src/app/page.tsx | 40 ++++----- apps/admin/src/app/providers.tsx | 26 +++--- apps/admin/src/env.ts | 42 ++++----- apps/admin/src/trpc/query-client.ts | 38 ++++---- apps/admin/src/trpc/react.tsx | 76 ++++++++-------- apps/admin/src/trpc/server.tsx | 26 +++--- apps/admin/tsconfig.json | 21 ++--- apps/api/next.config.ts | 45 +++++----- apps/api/package.json | 62 +++++++------- apps/api/src/app/api/trpc/[trpc]/route.ts | 18 ++-- apps/api/src/env.ts | 28 +++--- apps/api/tsconfig.json | 27 ++---- apps/marketing/next.config.ts | 10 +-- apps/marketing/package.json | 80 ++++++++--------- apps/marketing/postcss.config.mjs | 6 +- apps/marketing/tsconfig.json | 27 ++---- apps/web/next.config.ts | 8 +- apps/web/package.json | 86 +++++++++---------- apps/web/postcss.config.mjs | 6 +- apps/web/src/app/layout.tsx | 52 +++++------ apps/web/src/app/page.tsx | 100 +++++++++++----------- apps/web/src/app/providers.tsx | 26 +++--- apps/web/src/env.ts | 46 +++++----- apps/web/src/trpc/query-client.ts | 56 ++++++------ apps/web/src/trpc/react.tsx | 80 +++++++++-------- apps/web/src/trpc/server.tsx | 26 +++--- apps/web/tsconfig.json | 21 ++--- 31 files changed, 617 insertions(+), 614 deletions(-) diff --git a/apps/admin/next.config.ts b/apps/admin/next.config.ts index c9c33d2ed02..737f1468308 100644 --- a/apps/admin/next.config.ts +++ b/apps/admin/next.config.ts @@ -1,10 +1,10 @@ import type { NextConfig } from "next"; const config: NextConfig = { - experimental: { - reactCompiler: true, - }, - typescript: { ignoreBuildErrors: true }, + experimental: { + reactCompiler: true, + }, + typescript: { ignoreBuildErrors: true }, }; export default config; diff --git a/apps/admin/package.json b/apps/admin/package.json index dd8633d9eb7..36b92d1ffc9 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -1,45 +1,45 @@ { - "name": "@superset/admin", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "build": "next build", - "clean": "git clean -xdf .cache .next .turbo node_modules", - "dev": "next dev --port 3003", - "start": "next start", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@superset/db": "workspace:*", - "@superset/queries": "workspace:*", - "@superset/shared": "workspace:*", - "@superset/trpc": "workspace:*", - "@superset/ui": "workspace:*", - "@t3-oss/env-nextjs": "^0.13.8", - "@tanstack/react-query": "^5.90.10", - "@tanstack/react-query-devtools": "^5.90.10", - "@trpc/client": "^11.7.1", - "@trpc/server": "^11.7.1", - "@trpc/tanstack-react-query": "^11.7.1", - "geist": "^1.5.1", - "lucide-react": "^0.560.0", - "next": "^15.5.7", - "next-themes": "^0.4.6", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "server-only": "^0.0.1", - "superjson": "^2.2.5", - "zod": "^4.1.13" - }, - "devDependencies": { - "@superset/typescript": "workspace:*", - "@tailwindcss/postcss": "^4.0.9", - "@types/node": "^24.9.1", - "@types/react": "^19.1.11", - "@types/react-dom": "^19.1.7", - "babel-plugin-react-compiler": "^1.0.0", - "tailwindcss": "^4.0.9", - "typescript": "^5.9.3" - } + "name": "@superset/admin", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "git clean -xdf .cache .next .turbo node_modules", + "dev": "next dev --port 3003", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@superset/db": "workspace:*", + "@superset/queries": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", + "@superset/ui": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query-devtools": "^5.90.10", + "@trpc/client": "^11.7.1", + "@trpc/server": "^11.7.1", + "@trpc/tanstack-react-query": "^11.7.1", + "geist": "^1.5.1", + "lucide-react": "^0.560.0", + "next": "^15.5.7", + "next-themes": "^0.4.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "server-only": "^0.0.1", + "superjson": "^2.2.5", + "zod": "^4.1.13" + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@tailwindcss/postcss": "^4.0.9", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "babel-plugin-react-compiler": "^1.0.0", + "tailwindcss": "^4.0.9", + "typescript": "^5.9.3" + } } diff --git a/apps/admin/postcss.config.mjs b/apps/admin/postcss.config.mjs index c2ddf748220..017b34b9cb3 100644 --- a/apps/admin/postcss.config.mjs +++ b/apps/admin/postcss.config.mjs @@ -1,5 +1,5 @@ export default { - plugins: { - "@tailwindcss/postcss": {}, - }, + plugins: { + "@tailwindcss/postcss": {}, + }, }; diff --git a/apps/admin/src/app/layout.tsx b/apps/admin/src/app/layout.tsx index 564c4756248..47fe52f50ca 100644 --- a/apps/admin/src/app/layout.tsx +++ b/apps/admin/src/app/layout.tsx @@ -1,5 +1,5 @@ -import { cn } from "@superset/ui/utils"; import { Toaster } from "@superset/ui/sonner"; +import { cn } from "@superset/ui/utils"; import { GeistMono } from "geist/font/mono"; import { GeistSans } from "geist/font/sans"; import type { Metadata, Viewport } from "next"; @@ -9,32 +9,36 @@ import "./globals.css"; import { Providers } from "./providers"; export const metadata: Metadata = { - title: "Superset Admin", - description: "Admin dashboard for Superset", + title: "Superset Admin", + description: "Admin dashboard for Superset", }; export const viewport: Viewport = { - themeColor: [ - { media: "(prefers-color-scheme: light)", color: "white" }, - { media: "(prefers-color-scheme: dark)", color: "black" }, - ], + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], }; -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - {children} - - - - - ); +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + + + ); } diff --git a/apps/admin/src/app/page.tsx b/apps/admin/src/app/page.tsx index 5a134547095..00c034e5bf3 100644 --- a/apps/admin/src/app/page.tsx +++ b/apps/admin/src/app/page.tsx @@ -1,25 +1,25 @@ import { api } from "@/trpc/server"; export default async function Home() { - const users = await (await api()).user.all.query(); + const users = await (await api()).user.all.query(); - return ( -
-

Superset Admin

-

Admin dashboard

-
-

tRPC Test Query

-

- Users in database: {users.length} -

- {users.length > 0 && ( -
    - {users.slice(0, 5).map((user) => ( -
  • {user.email}
  • - ))} -
- )} -
-
- ); + return ( +
+

Superset Admin

+

Admin dashboard

+
+

tRPC Test Query

+

+ Users in database: {users.length} +

+ {users.length > 0 && ( +
    + {users.slice(0, 5).map((user) => ( +
  • {user.email}
  • + ))} +
+ )} +
+
+ ); } diff --git a/apps/admin/src/app/providers.tsx b/apps/admin/src/app/providers.tsx index bf3658269f0..4e0d68bfc9e 100644 --- a/apps/admin/src/app/providers.tsx +++ b/apps/admin/src/app/providers.tsx @@ -6,17 +6,17 @@ import { ThemeProvider } from "next-themes"; import { TRPCReactProvider } from "../trpc/react"; export function Providers({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - - ); + return ( + + + {children} + + + + ); } diff --git a/apps/admin/src/env.ts b/apps/admin/src/env.ts index 1ac5058d2f9..02472099543 100644 --- a/apps/admin/src/env.ts +++ b/apps/admin/src/env.ts @@ -3,28 +3,30 @@ import { vercel } from "@t3-oss/env-nextjs/presets-zod"; import { z } from "zod"; export const env = createEnv({ - extends: [vercel()], - shared: { - NODE_ENV: z.enum(["development", "production", "test"]).default("development"), - }, + extends: [vercel()], + shared: { + NODE_ENV: z + .enum(["development", "production", "test"]) + .default("development"), + }, - server: { - // Database (needed by @superset/trpc dependency) - DATABASE_URL: z.string().url(), - DATABASE_URL_UNPOOLED: z.string().url(), - }, + server: { + // Database (needed by @superset/trpc dependency) + DATABASE_URL: z.string().url(), + DATABASE_URL_UNPOOLED: z.string().url(), + }, - client: { - NEXT_PUBLIC_API_URL: z.string().url(), - NEXT_PUBLIC_WEB_URL: z.string().url().optional(), - }, + client: { + NEXT_PUBLIC_API_URL: z.string().url(), + NEXT_PUBLIC_WEB_URL: z.string().url().optional(), + }, - experimental__runtimeEnv: { - NODE_ENV: process.env.NODE_ENV, - NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, - NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL, - }, + experimental__runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL, + }, - skipValidation: !!process.env.SKIP_ENV_VALIDATION, - emptyStringAsUndefined: true, + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + emptyStringAsUndefined: true, }); diff --git a/apps/admin/src/trpc/query-client.ts b/apps/admin/src/trpc/query-client.ts index 7f3e4e1cfd0..bc21bfbd493 100644 --- a/apps/admin/src/trpc/query-client.ts +++ b/apps/admin/src/trpc/query-client.ts @@ -1,20 +1,24 @@ -import { defaultShouldDehydrateQuery, QueryClient } from "@tanstack/react-query"; +import { + defaultShouldDehydrateQuery, + QueryClient, +} from "@tanstack/react-query"; import SuperJSON from "superjson"; export const createQueryClient = () => - new QueryClient({ - defaultOptions: { - queries: { - staleTime: 30 * 1000, - }, - dehydrate: { - serializeData: SuperJSON.serialize, - shouldDehydrateQuery: (query) => - defaultShouldDehydrateQuery(query) || query.state.status === "pending", - shouldRedactErrors: () => false, - }, - hydrate: { - deserializeData: SuperJSON.deserialize, - }, - }, - }); + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30 * 1000, + }, + dehydrate: { + serializeData: SuperJSON.serialize, + shouldDehydrateQuery: (query) => + defaultShouldDehydrateQuery(query) || + query.state.status === "pending", + shouldRedactErrors: () => false, + }, + hydrate: { + deserializeData: SuperJSON.deserialize, + }, + }, + }); diff --git a/apps/admin/src/trpc/react.tsx b/apps/admin/src/trpc/react.tsx index 5132f0d018e..cd89546d215 100644 --- a/apps/admin/src/trpc/react.tsx +++ b/apps/admin/src/trpc/react.tsx @@ -3,7 +3,11 @@ import type { AppRouter } from "@superset/trpc"; import type { QueryClient } from "@tanstack/react-query"; import { QueryClientProvider } from "@tanstack/react-query"; -import { createTRPCClient, httpBatchStreamLink, loggerLink } from "@trpc/client"; +import { + createTRPCClient, + httpBatchStreamLink, + loggerLink, +} from "@trpc/client"; import { createTRPCContext } from "@trpc/tanstack-react-query"; import { useState } from "react"; import SuperJSON from "superjson"; @@ -13,13 +17,13 @@ import { createQueryClient } from "./query-client"; let clientQueryClientSingleton: QueryClient | undefined; const getQueryClient = () => { - if (typeof window === "undefined") { - return createQueryClient(); - } - if (!clientQueryClientSingleton) { - clientQueryClientSingleton = createQueryClient(); - } - return clientQueryClientSingleton; + if (typeof window === "undefined") { + return createQueryClient(); + } + if (!clientQueryClientSingleton) { + clientQueryClientSingleton = createQueryClient(); + } + return clientQueryClientSingleton; }; const context = createTRPCContext(); @@ -27,34 +31,34 @@ export const { useTRPC, TRPCProvider } = context; export type UseTRPC = typeof useTRPC; export function TRPCReactProvider(props: { children: React.ReactNode }) { - const queryClient = getQueryClient(); + const queryClient = getQueryClient(); - const [trpcClient] = useState(() => - createTRPCClient({ - links: [ - loggerLink({ - enabled: (op) => - env.NODE_ENV === "development" || - (op.direction === "down" && op.result instanceof Error), - }), - httpBatchStreamLink({ - transformer: SuperJSON, - url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, - headers() { - const headers = new Headers(); - headers.set("x-trpc-source", "nextjs-react"); - return headers; - }, - }), - ], - }), - ); + const [trpcClient] = useState(() => + createTRPCClient({ + links: [ + loggerLink({ + enabled: (op) => + env.NODE_ENV === "development" || + (op.direction === "down" && op.result instanceof Error), + }), + httpBatchStreamLink({ + transformer: SuperJSON, + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + const headers = new Headers(); + headers.set("x-trpc-source", "nextjs-react"); + return headers; + }, + }), + ], + }), + ); - return ( - - - {props.children} - - - ); + return ( + + + {props.children} + + + ); } diff --git a/apps/admin/src/trpc/server.tsx b/apps/admin/src/trpc/server.tsx index d842794bc2a..3042598abba 100644 --- a/apps/admin/src/trpc/server.tsx +++ b/apps/admin/src/trpc/server.tsx @@ -9,18 +9,18 @@ import SuperJSON from "superjson"; import { env } from "../env"; export const api = cache(async () => { - const heads = new Headers(await headers()); - heads.set("x-trpc-source", "rsc"); + const heads = new Headers(await headers()); + heads.set("x-trpc-source", "rsc"); - return createTRPCClient({ - links: [ - httpBatchLink({ - transformer: SuperJSON, - url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, - headers() { - return Object.fromEntries(heads.entries()); - }, - }), - ], - }); + return createTRPCClient({ + links: [ + httpBatchLink({ + transformer: SuperJSON, + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + return Object.fromEntries(heads.entries()); + }, + }), + ], + }); }); diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json index 87769fd1895..600980abb48 100644 --- a/apps/admin/tsconfig.json +++ b/apps/admin/tsconfig.json @@ -1,14 +1,11 @@ { - "extends": "@superset/typescript/base.json", - "compilerOptions": { - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "jsx": "preserve", - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - }, - "plugins": [{ "name": "next" }] - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "extends": "@superset/typescript/next.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/apps/api/next.config.ts b/apps/api/next.config.ts index b5e7fa7cbbf..8ea4db5a0d2 100644 --- a/apps/api/next.config.ts +++ b/apps/api/next.config.ts @@ -3,30 +3,31 @@ import type { NextConfig } from "next"; import { env } from "./src/env"; // Allowed origins for CORS -const allowedOrigins = [env.NEXT_PUBLIC_WEB_URL, env.NEXT_PUBLIC_ADMIN_URL].filter( - Boolean, -) as string[]; +const allowedOrigins = [ + env.NEXT_PUBLIC_WEB_URL, + env.NEXT_PUBLIC_ADMIN_URL, +].filter(Boolean) as string[]; const config: NextConfig = { - experimental: { - reactCompiler: true, - }, - typescript: { ignoreBuildErrors: true }, - async headers() { - // Generate CORS headers for each allowed origin - return allowedOrigins.map((origin) => ({ - source: "/api/:path*", - headers: [ - { key: "Access-Control-Allow-Origin", value: origin }, - { key: "Access-Control-Allow-Methods", value: "GET, POST, OPTIONS" }, - { - key: "Access-Control-Allow-Headers", - value: "Content-Type, Authorization, trpc-accept, x-trpc-source", - }, - { key: "Access-Control-Allow-Credentials", value: "true" }, - ], - })); - }, + experimental: { + reactCompiler: true, + }, + typescript: { ignoreBuildErrors: true }, + async headers() { + // Generate CORS headers for each allowed origin + return allowedOrigins.map((origin) => ({ + source: "/api/:path*", + headers: [ + { key: "Access-Control-Allow-Origin", value: origin }, + { key: "Access-Control-Allow-Methods", value: "GET, POST, OPTIONS" }, + { + key: "Access-Control-Allow-Headers", + value: "Content-Type, Authorization, trpc-accept, x-trpc-source", + }, + { key: "Access-Control-Allow-Credentials", value: "true" }, + ], + })); + }, }; export default config; diff --git a/apps/api/package.json b/apps/api/package.json index 745ee77572a..e83fde1b612 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,33 +1,33 @@ { - "name": "@superset/api", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "build": "next build", - "clean": "git clean -xdf .cache .next .turbo node_modules", - "dev": "next dev --port 3001", - "start": "next start --port 3001", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@superset/db": "workspace:*", - "@superset/shared": "workspace:*", - "@superset/trpc": "workspace:*", - "@t3-oss/env-nextjs": "^0.13.8", - "@trpc/server": "^11.7.1", - "drizzle-orm": "0.45.1", - "next": "^15.5.7", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "zod": "^4.1.13" - }, - "devDependencies": { - "@superset/typescript": "workspace:*", - "@types/node": "^24.9.1", - "@types/react": "^19.1.11", - "@types/react-dom": "^19.1.7", - "babel-plugin-react-compiler": "^1.0.0", - "typescript": "^5.9.3" - } + "name": "@superset/api", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "git clean -xdf .cache .next .turbo node_modules", + "dev": "next dev --port 3001", + "start": "next start --port 3001", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@superset/db": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "@trpc/server": "^11.7.1", + "drizzle-orm": "0.45.1", + "next": "^15.5.7", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "zod": "^4.1.13" + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "babel-plugin-react-compiler": "^1.0.0", + "typescript": "^5.9.3" + } } diff --git a/apps/api/src/app/api/trpc/[trpc]/route.ts b/apps/api/src/app/api/trpc/[trpc]/route.ts index f2d09fcc734..44e37e48e6f 100644 --- a/apps/api/src/app/api/trpc/[trpc]/route.ts +++ b/apps/api/src/app/api/trpc/[trpc]/route.ts @@ -2,15 +2,15 @@ import { appRouter, createTRPCContext } from "@superset/trpc"; import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; const handler = (req: Request) => - fetchRequestHandler({ - endpoint: "/api/trpc", - req, - router: appRouter, - createContext: () => createTRPCContext({ headers: req.headers }), - onError: ({ path, error }) => { - console.error(`โŒ tRPC error on ${path ?? ""}:`, error); - }, - }); + fetchRequestHandler({ + endpoint: "/api/trpc", + req, + router: appRouter, + createContext: () => createTRPCContext({ headers: req.headers }), + onError: ({ path, error }) => { + console.error(`โŒ tRPC error on ${path ?? ""}:`, error); + }, + }); // Preflight requests - CORS headers added by next.config.ts export const OPTIONS = () => new Response(null, { status: 204 }); diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts index 1bcb3ebc75a..090ad9bbbc2 100644 --- a/apps/api/src/env.ts +++ b/apps/api/src/env.ts @@ -2,18 +2,18 @@ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; export const env = createEnv({ - server: { - DATABASE_URL: z.string(), - DATABASE_URL_UNPOOLED: z.string(), - }, - client: { - NEXT_PUBLIC_WEB_URL: z.string().url(), - NEXT_PUBLIC_ADMIN_URL: z.string().url().optional(), - }, - experimental__runtimeEnv: { - NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL, - NEXT_PUBLIC_ADMIN_URL: process.env.NEXT_PUBLIC_ADMIN_URL, - }, - emptyStringAsUndefined: true, - skipValidation: !!process.env.SKIP_ENV_VALIDATION, + server: { + DATABASE_URL: z.string(), + DATABASE_URL_UNPOOLED: z.string(), + }, + client: { + NEXT_PUBLIC_WEB_URL: z.string().url(), + NEXT_PUBLIC_ADMIN_URL: z.string().url().optional(), + }, + experimental__runtimeEnv: { + NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL, + NEXT_PUBLIC_ADMIN_URL: process.env.NEXT_PUBLIC_ADMIN_URL, + }, + emptyStringAsUndefined: true, + skipValidation: !!process.env.SKIP_ENV_VALIDATION, }); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index fec43826096..600980abb48 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -1,20 +1,11 @@ { - "extends": "@superset/typescript/base.json", - "compilerOptions": { - "plugins": [ - { - "name": "next" - } - ], - "module": "ESNext", - "moduleResolution": "Bundler", - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", - "jsx": "preserve", - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "extends": "@superset/typescript/next.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/apps/marketing/next.config.ts b/apps/marketing/next.config.ts index b74ea3041fc..3b3de1463ba 100644 --- a/apps/marketing/next.config.ts +++ b/apps/marketing/next.config.ts @@ -1,11 +1,11 @@ import type { NextConfig } from "next"; const config: NextConfig = { - reactStrictMode: true, - experimental: { - reactCompiler: true, - }, - typescript: { ignoreBuildErrors: true }, + reactStrictMode: true, + experimental: { + reactCompiler: true, + }, + typescript: { ignoreBuildErrors: true }, }; export default config; diff --git a/apps/marketing/package.json b/apps/marketing/package.json index c3b016bb93a..f843bfa025b 100644 --- a/apps/marketing/package.json +++ b/apps/marketing/package.json @@ -1,42 +1,42 @@ { - "name": "@superset/marketing", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "build": "next build", - "clean": "git clean -xdf .cache .next .turbo node_modules", - "dev": "next dev --port 3002", - "start": "next start", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@react-three/drei": "^10.7.6", - "@react-three/fiber": "^9.4.0", - "@superset/ui": "workspace:*", - "@t3-oss/env-nextjs": "^0.13.8", - "framer-motion": "^12.23.24", - "geist": "^1.5.1", - "lucide-react": "^0.560.0", - "next": "^15.5.7", - "next-themes": "^0.4.6", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "react-fast-marquee": "^1.6.5", - "react-icons": "^5.5.0", - "three": "^0.181.2", - "zod": "^4.1.13" - }, - "devDependencies": { - "@superset/typescript": "workspace:*", - "@tailwindcss/postcss": "^4.0.9", - "@types/mdx": "^2.0.13", - "@types/node": "^24.9.1", - "@types/react": "^19.1.11", - "@types/react-dom": "^19.1.7", - "@types/three": "^0.181.0", - "babel-plugin-react-compiler": "^1.0.0", - "tailwindcss": "^4.0.9", - "typescript": "^5.9.3" - } + "name": "@superset/marketing", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "git clean -xdf .cache .next .turbo node_modules", + "dev": "next dev --port 3002", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@react-three/drei": "^10.7.6", + "@react-three/fiber": "^9.4.0", + "@superset/ui": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "framer-motion": "^12.23.24", + "geist": "^1.5.1", + "lucide-react": "^0.560.0", + "next": "^15.5.7", + "next-themes": "^0.4.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-fast-marquee": "^1.6.5", + "react-icons": "^5.5.0", + "three": "^0.181.2", + "zod": "^4.1.13" + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@tailwindcss/postcss": "^4.0.9", + "@types/mdx": "^2.0.13", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "@types/three": "^0.181.0", + "babel-plugin-react-compiler": "^1.0.0", + "tailwindcss": "^4.0.9", + "typescript": "^5.9.3" + } } diff --git a/apps/marketing/postcss.config.mjs b/apps/marketing/postcss.config.mjs index c2ddf748220..017b34b9cb3 100644 --- a/apps/marketing/postcss.config.mjs +++ b/apps/marketing/postcss.config.mjs @@ -1,5 +1,5 @@ export default { - plugins: { - "@tailwindcss/postcss": {}, - }, + plugins: { + "@tailwindcss/postcss": {}, + }, }; diff --git a/apps/marketing/tsconfig.json b/apps/marketing/tsconfig.json index fec43826096..600980abb48 100644 --- a/apps/marketing/tsconfig.json +++ b/apps/marketing/tsconfig.json @@ -1,20 +1,11 @@ { - "extends": "@superset/typescript/base.json", - "compilerOptions": { - "plugins": [ - { - "name": "next" - } - ], - "module": "ESNext", - "moduleResolution": "Bundler", - "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", - "jsx": "preserve", - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, - "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "extends": "@superset/typescript/next.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index c9c33d2ed02..737f1468308 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -1,10 +1,10 @@ import type { NextConfig } from "next"; const config: NextConfig = { - experimental: { - reactCompiler: true, - }, - typescript: { ignoreBuildErrors: true }, + experimental: { + reactCompiler: true, + }, + typescript: { ignoreBuildErrors: true }, }; export default config; diff --git a/apps/web/package.json b/apps/web/package.json index 56fcfdced87..8de561eb4a4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,45 +1,45 @@ { - "name": "@superset/web", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "build": "next build", - "clean": "git clean -xdf .cache .next .turbo node_modules", - "dev": "next dev", - "start": "next start", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@superset/db": "workspace:*", - "@superset/queries": "workspace:*", - "@superset/shared": "workspace:*", - "@superset/trpc": "workspace:*", - "@superset/ui": "workspace:*", - "@t3-oss/env-nextjs": "^0.13.8", - "@tanstack/react-query": "^5.90.10", - "@tanstack/react-query-devtools": "^5.90.10", - "@trpc/client": "^11.7.1", - "@trpc/server": "^11.7.1", - "@trpc/tanstack-react-query": "^11.7.1", - "geist": "^1.5.1", - "lucide-react": "^0.560.0", - "next": "^15.5.7", - "next-themes": "^0.4.6", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "server-only": "^0.0.1", - "superjson": "^2.2.5", - "zod": "^4.1.13" - }, - "devDependencies": { - "@superset/typescript": "workspace:*", - "@tailwindcss/postcss": "^4.0.9", - "@types/node": "^24.9.1", - "@types/react": "^19.1.11", - "@types/react-dom": "^19.1.7", - "babel-plugin-react-compiler": "^1.0.0", - "tailwindcss": "^4.0.9", - "typescript": "^5.9.3" - } + "name": "@superset/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build", + "clean": "git clean -xdf .cache .next .turbo node_modules", + "dev": "next dev", + "start": "next start", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@superset/db": "workspace:*", + "@superset/queries": "workspace:*", + "@superset/shared": "workspace:*", + "@superset/trpc": "workspace:*", + "@superset/ui": "workspace:*", + "@t3-oss/env-nextjs": "^0.13.8", + "@tanstack/react-query": "^5.90.10", + "@tanstack/react-query-devtools": "^5.90.10", + "@trpc/client": "^11.7.1", + "@trpc/server": "^11.7.1", + "@trpc/tanstack-react-query": "^11.7.1", + "geist": "^1.5.1", + "lucide-react": "^0.560.0", + "next": "^15.5.7", + "next-themes": "^0.4.6", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "server-only": "^0.0.1", + "superjson": "^2.2.5", + "zod": "^4.1.13" + }, + "devDependencies": { + "@superset/typescript": "workspace:*", + "@tailwindcss/postcss": "^4.0.9", + "@types/node": "^24.9.1", + "@types/react": "^19.1.11", + "@types/react-dom": "^19.1.7", + "babel-plugin-react-compiler": "^1.0.0", + "tailwindcss": "^4.0.9", + "typescript": "^5.9.3" + } } diff --git a/apps/web/postcss.config.mjs b/apps/web/postcss.config.mjs index c2ddf748220..017b34b9cb3 100644 --- a/apps/web/postcss.config.mjs +++ b/apps/web/postcss.config.mjs @@ -1,5 +1,5 @@ export default { - plugins: { - "@tailwindcss/postcss": {}, - }, + plugins: { + "@tailwindcss/postcss": {}, + }, }; diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 909a1b44e7c..6614041bfbb 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,5 +1,5 @@ -import { cn } from "@superset/ui/utils"; import { Toaster } from "@superset/ui/sonner"; +import { cn } from "@superset/ui/utils"; import { GeistMono } from "geist/font/mono"; import { GeistSans } from "geist/font/sans"; import type { Metadata, Viewport } from "next"; @@ -9,32 +9,36 @@ import "./globals.css"; import { Providers } from "./providers"; export const metadata: Metadata = { - title: "Superset", - description: "Run 10+ parallel coding agents on your machine", + title: "Superset", + description: "Run 10+ parallel coding agents on your machine", }; export const viewport: Viewport = { - themeColor: [ - { media: "(prefers-color-scheme: light)", color: "white" }, - { media: "(prefers-color-scheme: dark)", color: "black" }, - ], + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], }; -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - {children} - - - - - ); +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + + + ); } diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index cc8557645e0..4e154f2cd0c 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,60 +1,58 @@ import { api } from "@/trpc/server"; export default async function Home() { - const trpc = await api(); + const trpc = await api(); - const users = await trpc.user.all.query(); + const users = await trpc.user.all.query(); - let me = null; - let authError = null; - try { - me = await trpc.user.me.query(); - } catch (e) { - authError = e instanceof Error ? e.message : "Unknown error"; - } + let me = null; + let authError = null; + try { + me = await trpc.user.me.query(); + } catch (e) { + authError = e instanceof Error ? e.message : "Unknown error"; + } - return ( -
-

Superset Web

-

- Run 10+ parallel coding agents on your machine -

+ return ( +
+

Superset Web

+

+ Run 10+ parallel coding agents on your machine +

-
-
-

Public Query

-

user.all()

-

- Users in database: {users.length} -

- {users.length > 0 && ( -
    - {users.slice(0, 3).map((user) => ( -
  • {user.email}
  • - ))} -
- )} -
+
+
+

Public Query

+

user.all()

+

+ Users in database: {users.length} +

+ {users.length > 0 && ( +
    + {users.slice(0, 3).map((user) => ( +
  • {user.email}
  • + ))} +
+ )} +
-
-

Protected Query

-

user.me()

- {authError ? ( -

- Error: {authError} -

- ) : me ? ( -
-

Logged in as: {me.email}

-

{me.name}

-
- ) : ( -

- No user found for MOCK_USER_ID -

- )} -
-
-
- ); +
+

Protected Query

+

user.me()

+ {authError ? ( +

Error: {authError}

+ ) : me ? ( +
+

Logged in as: {me.email}

+

{me.name}

+
+ ) : ( +

+ No user found for MOCK_USER_ID +

+ )} +
+ +
+ ); } diff --git a/apps/web/src/app/providers.tsx b/apps/web/src/app/providers.tsx index 99deeb2dd13..aa341abf5cf 100644 --- a/apps/web/src/app/providers.tsx +++ b/apps/web/src/app/providers.tsx @@ -6,17 +6,17 @@ import { ThemeProvider } from "next-themes"; import { TRPCReactProvider } from "../trpc/react"; export function Providers({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - - ); + return ( + + + {children} + + + + ); } diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts index ec854e90682..a8eb4893662 100644 --- a/apps/web/src/env.ts +++ b/apps/web/src/env.ts @@ -3,30 +3,32 @@ import { vercel } from "@t3-oss/env-nextjs/presets-zod"; import { z } from "zod"; export const env = createEnv({ - extends: [vercel()], - shared: { - NODE_ENV: z.enum(["development", "production", "test"]).default("development"), - }, + extends: [vercel()], + shared: { + NODE_ENV: z + .enum(["development", "production", "test"]) + .default("development"), + }, - server: { - // Database (needed by @superset/trpc dependency) - DATABASE_URL: z.string().url(), - DATABASE_URL_UNPOOLED: z.string().url(), - }, + server: { + // Database (needed by @superset/trpc dependency) + DATABASE_URL: z.string().url(), + DATABASE_URL_UNPOOLED: z.string().url(), + }, - client: { - NEXT_PUBLIC_API_URL: z.string().url(), - NEXT_PUBLIC_MARKETING_URL: z.string().url().optional(), - NEXT_PUBLIC_DOCS_URL: z.string().url().optional(), - }, + client: { + NEXT_PUBLIC_API_URL: z.string().url(), + NEXT_PUBLIC_MARKETING_URL: z.string().url().optional(), + NEXT_PUBLIC_DOCS_URL: z.string().url().optional(), + }, - experimental__runtimeEnv: { - NODE_ENV: process.env.NODE_ENV, - NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, - NEXT_PUBLIC_MARKETING_URL: process.env.NEXT_PUBLIC_MARKETING_URL, - NEXT_PUBLIC_DOCS_URL: process.env.NEXT_PUBLIC_DOCS_URL, - }, + experimental__runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, + NEXT_PUBLIC_MARKETING_URL: process.env.NEXT_PUBLIC_MARKETING_URL, + NEXT_PUBLIC_DOCS_URL: process.env.NEXT_PUBLIC_DOCS_URL, + }, - skipValidation: !!process.env.SKIP_ENV_VALIDATION, - emptyStringAsUndefined: true, + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + emptyStringAsUndefined: true, }); diff --git a/apps/web/src/trpc/query-client.ts b/apps/web/src/trpc/query-client.ts index d5e339e9d20..5780964e5e4 100644 --- a/apps/web/src/trpc/query-client.ts +++ b/apps/web/src/trpc/query-client.ts @@ -1,29 +1,33 @@ -import { defaultShouldDehydrateQuery, QueryClient } from "@tanstack/react-query"; +import { + defaultShouldDehydrateQuery, + QueryClient, +} from "@tanstack/react-query"; import SuperJSON from "superjson"; export const createQueryClient = () => - new QueryClient({ - defaultOptions: { - queries: { - // With SSR, we usually want to set some default staleTime - // above 0 to avoid refetching immediately on the client - staleTime: 30 * 1000, - }, - dehydrate: { - serializeData: SuperJSON.serialize, - shouldDehydrateQuery: (query) => - defaultShouldDehydrateQuery(query) || query.state.status === "pending", - shouldRedactErrors: () => { - // We should not catch Next.js server errors - // as that's how Next.js detects dynamic pages - // so we cannot redact them. - // Next.js also automatically redacts errors for us - // with better digests. - return false; - }, - }, - hydrate: { - deserializeData: SuperJSON.deserialize, - }, - }, - }); + new QueryClient({ + defaultOptions: { + queries: { + // With SSR, we usually want to set some default staleTime + // above 0 to avoid refetching immediately on the client + staleTime: 30 * 1000, + }, + dehydrate: { + serializeData: SuperJSON.serialize, + shouldDehydrateQuery: (query) => + defaultShouldDehydrateQuery(query) || + query.state.status === "pending", + shouldRedactErrors: () => { + // We should not catch Next.js server errors + // as that's how Next.js detects dynamic pages + // so we cannot redact them. + // Next.js also automatically redacts errors for us + // with better digests. + return false; + }, + }, + hydrate: { + deserializeData: SuperJSON.deserialize, + }, + }, + }); diff --git a/apps/web/src/trpc/react.tsx b/apps/web/src/trpc/react.tsx index 34f55a4de22..a910f0201a7 100644 --- a/apps/web/src/trpc/react.tsx +++ b/apps/web/src/trpc/react.tsx @@ -3,7 +3,11 @@ import type { AppRouter } from "@superset/trpc"; import type { QueryClient } from "@tanstack/react-query"; import { QueryClientProvider } from "@tanstack/react-query"; -import { createTRPCClient, httpBatchStreamLink, loggerLink } from "@trpc/client"; +import { + createTRPCClient, + httpBatchStreamLink, + loggerLink, +} from "@trpc/client"; import { createTRPCContext } from "@trpc/tanstack-react-query"; import { useState } from "react"; import SuperJSON from "superjson"; @@ -13,15 +17,15 @@ import { createQueryClient } from "./query-client"; let clientQueryClientSingleton: QueryClient | undefined; const getQueryClient = () => { - if (typeof window === "undefined") { - // Server: always make a new query client - return createQueryClient(); - } - // Browser: use singleton pattern to keep the same query client - if (!clientQueryClientSingleton) { - clientQueryClientSingleton = createQueryClient(); - } - return clientQueryClientSingleton; + if (typeof window === "undefined") { + // Server: always make a new query client + return createQueryClient(); + } + // Browser: use singleton pattern to keep the same query client + if (!clientQueryClientSingleton) { + clientQueryClientSingleton = createQueryClient(); + } + return clientQueryClientSingleton; }; const context = createTRPCContext(); @@ -29,34 +33,34 @@ export const { useTRPC, TRPCProvider } = context; export type UseTRPC = typeof useTRPC; export function TRPCReactProvider(props: { children: React.ReactNode }) { - const queryClient = getQueryClient(); + const queryClient = getQueryClient(); - const [trpcClient] = useState(() => - createTRPCClient({ - links: [ - loggerLink({ - enabled: (op) => - env.NODE_ENV === "development" || - (op.direction === "down" && op.result instanceof Error), - }), - httpBatchStreamLink({ - transformer: SuperJSON, - url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, - headers() { - const headers = new Headers(); - headers.set("x-trpc-source", "nextjs-react"); - return headers; - }, - }), - ], - }), - ); + const [trpcClient] = useState(() => + createTRPCClient({ + links: [ + loggerLink({ + enabled: (op) => + env.NODE_ENV === "development" || + (op.direction === "down" && op.result instanceof Error), + }), + httpBatchStreamLink({ + transformer: SuperJSON, + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + const headers = new Headers(); + headers.set("x-trpc-source", "nextjs-react"); + return headers; + }, + }), + ], + }), + ); - return ( - - - {props.children} - - - ); + return ( + + + {props.children} + + + ); } diff --git a/apps/web/src/trpc/server.tsx b/apps/web/src/trpc/server.tsx index d842794bc2a..3042598abba 100644 --- a/apps/web/src/trpc/server.tsx +++ b/apps/web/src/trpc/server.tsx @@ -9,18 +9,18 @@ import SuperJSON from "superjson"; import { env } from "../env"; export const api = cache(async () => { - const heads = new Headers(await headers()); - heads.set("x-trpc-source", "rsc"); + const heads = new Headers(await headers()); + heads.set("x-trpc-source", "rsc"); - return createTRPCClient({ - links: [ - httpBatchLink({ - transformer: SuperJSON, - url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, - headers() { - return Object.fromEntries(heads.entries()); - }, - }), - ], - }); + return createTRPCClient({ + links: [ + httpBatchLink({ + transformer: SuperJSON, + url: `${env.NEXT_PUBLIC_API_URL}/api/trpc`, + headers() { + return Object.fromEntries(heads.entries()); + }, + }), + ], + }); }); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 87769fd1895..600980abb48 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -1,14 +1,11 @@ { - "extends": "@superset/typescript/base.json", - "compilerOptions": { - "lib": ["ES2022", "DOM", "DOM.Iterable"], - "jsx": "preserve", - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - }, - "plugins": [{ "name": "next" }] - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "extends": "@superset/typescript/next.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } From 1c52c848d86b8653ac2383a892301d1bb44645ef Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Thu, 11 Dec 2025 23:53:10 -0500 Subject: [PATCH 3/5] WIP --- .github/templates/cleanup-comment.md | 19 ++ .github/templates/preview-comment.md | 46 +++ .github/workflows/ci.yml | 85 ++--- .github/workflows/cleanup-preview.yml | 24 +- .github/workflows/deploy-preview.yml | 435 ++++++++++++++++++++++-- .github/workflows/deploy-production.yml | 221 +++++++++++- apps/admin/src/env.ts | 2 +- apps/api/src/env.ts | 2 +- apps/marketing/src/env.ts | 2 +- apps/web/src/env.ts | 4 +- packages/trpc/src/env.ts | 2 +- 11 files changed, 742 insertions(+), 100 deletions(-) create mode 100644 .github/templates/cleanup-comment.md create mode 100644 .github/templates/preview-comment.md diff --git a/.github/templates/cleanup-comment.md b/.github/templates/cleanup-comment.md new file mode 100644 index 00000000000..3c5c8d78293 --- /dev/null +++ b/.github/templates/cleanup-comment.md @@ -0,0 +1,19 @@ +## ๐Ÿงน Preview Cleanup Complete + +The following preview resources have been cleaned up: + + + + + + + + + + +
ServiceStatus
Database (Neon)$NEON_STATUS
+ +Thank you for your contribution! ๐ŸŽ‰ + +--- +*Preview resources have been processed for cleanup* diff --git a/.github/templates/preview-comment.md b/.github/templates/preview-comment.md new file mode 100644 index 00000000000..a428ba0f689 --- /dev/null +++ b/.github/templates/preview-comment.md @@ -0,0 +1,46 @@ +## ๐Ÿš€ Preview Deployment + +### ๐Ÿ”— Preview Links + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ServiceStatusLink
Database (Neon)$DATABASE_STATUS$DATABASE_LINK
API (Vercel)$API_STATUS$API_LINK
Web (Vercel)$WEB_STATUS$WEB_LINK
Marketing (Vercel)$MARKETING_STATUS$MARKETING_LINK
Admin (Vercel)$ADMIN_STATUS$ADMIN_LINK
Docs (Vercel)$DOCS_STATUS$DOCS_LINK
+ +--- + +*Preview updates automatically with new commits* + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ae949f4fe4..e381389c701 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,107 +1,80 @@ name: CI on: - pull_request: push: branches: [main] + pull_request: + types: [opened, synchronize] jobs: lint: name: Lint runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + - name: Check out code + uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.3 - name: Cache dependencies uses: actions/cache@v4 with: - path: | - ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} - name: Install dependencies run: bun install --frozen - - name: Run lint + - name: Lint run: bun run lint - typecheck: - name: Typecheck - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.3 - - - name: Cache dependencies - uses: actions/cache@v4 - with: - path: | - ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} - - - name: Install dependencies - run: bun install --frozen - - - name: Run typecheck - run: bun run typecheck - - build: - name: Build + test: + name: Test runs-on: ubuntu-latest - env: - # Placeholder values for build validation (not used at runtime) - DATABASE_URL: "postgresql://placeholder:placeholder@localhost:5432/placeholder" - DATABASE_URL_UNPOOLED: "postgresql://placeholder:placeholder@localhost:5432/placeholder" - steps: - - uses: actions/checkout@v4 + - name: Check out code + uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.3 - name: Cache dependencies uses: actions/cache@v4 with: - path: | - ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} - name: Install dependencies run: bun install --frozen - - name: Run build - run: bun run build + - name: Test + run: bun run test - test: - name: Test + typecheck: + name: Typecheck runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + - name: Check out code + uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.3 - name: Cache dependencies uses: actions/cache@v4 with: - path: | - ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} - name: Install dependencies run: bun install --frozen - - name: Run test - run: bun run test + - name: Typecheck + run: bun run typecheck diff --git a/.github/workflows/cleanup-preview.yml b/.github/workflows/cleanup-preview.yml index 70db7f827de..5ee7f917576 100644 --- a/.github/workflows/cleanup-preview.yml +++ b/.github/workflows/cleanup-preview.yml @@ -11,25 +11,29 @@ jobs: permissions: contents: read pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Delete Neon branch - id: delete-branch - uses: neondatabase/delete-branch-action@v3.2.0 + id: neon-cleanup + uses: neondatabase/delete-branch-action@v3 continue-on-error: true with: project_id: ${{ vars.NEON_PROJECT_ID }} branch: ${{ github.event.pull_request.head.ref }} api_key: ${{ secrets.NEON_API_KEY }} - - name: Update comment with cleanup status + - name: Generate cleanup comment + run: | + NEON_STATUS="${{ steps.neon-cleanup.outcome == 'success' && 'โœ…' || 'โš ๏ธ' }}" + export NEON_STATUS + envsubst < .github/templates/cleanup-comment.md > final-comment.md + + - name: Update comment if: always() uses: thollander/actions-comment-pull-request@v3 with: + file-path: final-comment.md comment-tag: "๐Ÿš€-preview-deployment" - message: | - ## ๐Ÿงน Preview Cleanup Complete - - The following preview resources have been cleaned up: - - ${{ steps.delete-branch.outcome == 'success' && 'โœ…' || 'โš ๏ธ' }} Neon database branch - - Thank you for your contribution! ๐ŸŽ‰ diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 2236b32d833..76b3783ed56 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -8,17 +8,25 @@ concurrency: group: preview-${{ github.event.pull_request.number }} cancel-in-progress: true +env: + PR_NUMBER: ${{ github.event.pull_request.number }} + API_ALIAS: api-pr-${{ github.event.pull_request.number }}-superset.vercel.app + WEB_ALIAS: web-pr-${{ github.event.pull_request.number }}-superset.vercel.app + MARKETING_ALIAS: marketing-pr-${{ github.event.pull_request.number }}-superset.vercel.app + ADMIN_ALIAS: admin-pr-${{ github.event.pull_request.number }}-superset.vercel.app + DOCS_ALIAS: docs-pr-${{ github.event.pull_request.number }}-superset.vercel.app + jobs: - create-neon-branch: - name: Create Neon Branch + deploy-database: + name: Deploy Database (Neon) runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write + steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.3 @@ -26,20 +34,20 @@ jobs: uses: actions/cache@v4 with: path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} - name: Install dependencies run: bun install --frozen - name: Create Neon branch id: create-branch - uses: neondatabase/create-branch-action@v6.1.1 + uses: neondatabase/create-branch-action@v6 with: project_id: ${{ vars.NEON_PROJECT_ID }} branch_name: ${{ github.head_ref }} api_key: ${{ secrets.NEON_API_KEY }} - - name: Validate and run migrations + - name: Validate and push database schema working-directory: packages/db env: DATABASE_URL: ${{ steps.create-branch.outputs.db_url_pooled }} @@ -48,19 +56,404 @@ jobs: bun drizzle-kit check bun drizzle-kit migrate - - name: Post PR comment - uses: thollander/actions-comment-pull-request@v3 + - name: Save database success status + run: | + cat > database-status.env << EOF + DATABASE_STATUS="โœ…" + DATABASE_LINK="View Branch" + DATABASE_URL="${{ steps.create-branch.outputs.db_url_pooled }}" + DATABASE_URL_UNPOOLED="${{ steps.create-branch.outputs.db_url }}" + BRANCH_ID="${{ steps.create-branch.outputs.branch_id }}" + EOF + + - name: Upload database status + uses: actions/upload-artifact@v4 with: - comment-tag: "๐Ÿš€-preview-deployment" - message: | - ## ๐Ÿš€ Preview Deployment + name: database-status + path: database-status.env + + deploy-api: + name: Deploy API + runs-on: ubuntu-latest + environment: preview + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Download database info + uses: actions/download-artifact@v4 + with: + name: database-status + + - name: Load database URL + run: | + source database-status.env + echo "DATABASE_URL=$DATABASE_URL" >> $GITHUB_ENV + echo "DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED" >> $GITHUB_ENV + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy API + id: deploy + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_API_PROJECT_ID }} + DATABASE_URL: ${{ env.DATABASE_URL }} + DATABASE_URL_UNPOOLED: ${{ env.DATABASE_URL_UNPOOLED }} + NEXT_PUBLIC_WEB_URL: https://${{ env.WEB_ALIAS }} + NEXT_PUBLIC_ADMIN_URL: https://${{ env.ADMIN_ALIAS }} + MOCK_USER_ID: ${{ secrets.MOCK_USER_ID }} + run: | + vercel pull --yes --environment=preview --token=$VERCEL_TOKEN + vercel build --token=$VERCEL_TOKEN + VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN) + vercel alias $VERCEL_URL ${{ env.API_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN + echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT + + - name: Save API success status + run: | + cat > api-status.env << EOF + API_STATUS="โœ…" + API_LINK="Open Preview" + API_URL="https://${{ env.API_ALIAS }}" + EOF + + - name: Upload API status + uses: actions/upload-artifact@v4 + with: + name: api-status + path: api-status.env + + deploy-web: + name: Deploy Web + runs-on: ubuntu-latest + environment: preview + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Download database info + uses: actions/download-artifact@v4 + with: + name: database-status + + - name: Load database URL + run: | + source database-status.env + echo "DATABASE_URL=$DATABASE_URL" >> $GITHUB_ENV + echo "DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED" >> $GITHUB_ENV + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy Web + id: deploy + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_WEB_PROJECT_ID }} + DATABASE_URL: ${{ env.DATABASE_URL }} + DATABASE_URL_UNPOOLED: ${{ env.DATABASE_URL_UNPOOLED }} + NEXT_PUBLIC_API_URL: https://${{ env.API_ALIAS }} + NEXT_PUBLIC_MARKETING_URL: https://${{ env.MARKETING_ALIAS }} + NEXT_PUBLIC_DOCS_URL: https://${{ env.DOCS_ALIAS }} + MOCK_USER_ID: ${{ secrets.MOCK_USER_ID }} + run: | + vercel pull --yes --environment=preview --token=$VERCEL_TOKEN + vercel build --token=$VERCEL_TOKEN + VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN) + vercel alias $VERCEL_URL ${{ env.WEB_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN + echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT + + - name: Save web success status + run: | + cat > web-status.env << EOF + WEB_STATUS="โœ…" + WEB_LINK="Open Preview" + EOF + + - name: Upload web status + uses: actions/upload-artifact@v4 + with: + name: web-status + path: web-status.env + + deploy-marketing: + name: Deploy Marketing + runs-on: ubuntu-latest + environment: preview + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy Marketing + id: deploy + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_MARKETING_PROJECT_ID }} + NEXT_PUBLIC_API_URL: https://${{ env.API_ALIAS }} + run: | + vercel pull --yes --environment=preview --token=$VERCEL_TOKEN + vercel build --token=$VERCEL_TOKEN + VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN) + vercel alias $VERCEL_URL ${{ env.MARKETING_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN + echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT + + - name: Save marketing success status + run: | + cat > marketing-status.env << EOF + MARKETING_STATUS="โœ…" + MARKETING_LINK="Open Preview" + EOF + + - name: Upload marketing status + uses: actions/upload-artifact@v4 + with: + name: marketing-status + path: marketing-status.env + + deploy-admin: + name: Deploy Admin + runs-on: ubuntu-latest + environment: preview + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 - ### ๐Ÿ”— Preview Links + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 - | Service | Status | Link | - |---------|:------:|------| - | **Database (Neon)** | โœ… | [View Branch](https://console.neon.tech/app/projects/${{ vars.NEON_PROJECT_ID }}/branches/${{ steps.create-branch.outputs.branch_id }}/tables) | + - name: Download database info + uses: actions/download-artifact@v4 + with: + name: database-status - --- + - name: Load database URL + run: | + source database-status.env + echo "DATABASE_URL=$DATABASE_URL" >> $GITHUB_ENV + echo "DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED" >> $GITHUB_ENV - *Preview updates automatically with new commits* + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy Admin + id: deploy + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_ADMIN_PROJECT_ID }} + DATABASE_URL: ${{ env.DATABASE_URL }} + DATABASE_URL_UNPOOLED: ${{ env.DATABASE_URL_UNPOOLED }} + NEXT_PUBLIC_API_URL: https://${{ env.API_ALIAS }} + NEXT_PUBLIC_WEB_URL: https://${{ env.WEB_ALIAS }} + MOCK_USER_ID: ${{ secrets.MOCK_USER_ID }} + run: | + vercel pull --yes --environment=preview --token=$VERCEL_TOKEN + vercel build --token=$VERCEL_TOKEN + VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN) + vercel alias $VERCEL_URL ${{ env.ADMIN_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN + echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT + + - name: Save admin success status + run: | + cat > admin-status.env << EOF + ADMIN_STATUS="โœ…" + ADMIN_LINK="Open Preview" + EOF + + - name: Upload admin status + uses: actions/upload-artifact@v4 + with: + name: admin-status + path: admin-status.env + + deploy-docs: + name: Deploy Docs + runs-on: ubuntu-latest + environment: preview + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy Docs + id: deploy + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_DOCS_PROJECT_ID }} + NEXT_PUBLIC_API_URL: https://${{ env.API_ALIAS }} + NEXT_PUBLIC_WEB_URL: https://${{ env.WEB_ALIAS }} + NEXT_PUBLIC_MARKETING_URL: https://${{ env.MARKETING_ALIAS }} + run: | + vercel pull --yes --environment=preview --token=$VERCEL_TOKEN + vercel build --token=$VERCEL_TOKEN + VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN) + vercel alias $VERCEL_URL ${{ env.DOCS_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN + echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT + + - name: Save docs success status + run: | + cat > docs-status.env << EOF + DOCS_STATUS="โœ…" + DOCS_LINK="Open Preview" + EOF + + - name: Upload docs status + uses: actions/upload-artifact@v4 + with: + name: docs-status + path: docs-status.env + + post-final-comment: + name: Post Deployment Comment + runs-on: ubuntu-latest + if: always() + needs: [deploy-database, deploy-api, deploy-web, deploy-marketing, deploy-admin, deploy-docs] + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download all status artifacts + uses: actions/download-artifact@v4 + with: + pattern: "*-status" + merge-multiple: true + + - name: Generate deployment comment + run: | + DATABASE_STATUS="โŒ" + DATABASE_LINK="Failed to create" + API_STATUS="โŒ" + API_LINK="Failed to deploy" + WEB_STATUS="โŒ" + WEB_LINK="Failed to deploy" + MARKETING_STATUS="โŒ" + MARKETING_LINK="Failed to deploy" + ADMIN_STATUS="โŒ" + ADMIN_LINK="Failed to deploy" + DOCS_STATUS="โŒ" + DOCS_LINK="Failed to deploy" + + if [[ "${{ needs.deploy-database.result }}" == "success" ]]; then + source database-status.env + fi + + if [[ "${{ needs.deploy-api.result }}" == "success" ]]; then + source api-status.env + fi + + if [[ "${{ needs.deploy-web.result }}" == "success" ]]; then + source web-status.env + fi + + if [[ "${{ needs.deploy-marketing.result }}" == "success" ]]; then + source marketing-status.env + fi + + if [[ "${{ needs.deploy-admin.result }}" == "success" ]]; then + source admin-status.env + fi + + if [[ "${{ needs.deploy-docs.result }}" == "success" ]]; then + source docs-status.env + fi + + export DATABASE_STATUS DATABASE_LINK API_STATUS API_LINK WEB_STATUS WEB_LINK MARKETING_STATUS MARKETING_LINK ADMIN_STATUS ADMIN_LINK DOCS_STATUS DOCS_LINK + envsubst < .github/templates/preview-comment.md > final-comment.md + + - name: Post final deployment comment + uses: thollander/actions-comment-pull-request@v3 + with: + file-path: final-comment.md + comment-tag: "๐Ÿš€-preview-deployment" diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 67d4a351c32..ec00f4ced67 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -3,31 +3,238 @@ name: Deploy Production on: push: branches: [main] + workflow_dispatch: jobs: deploy-database: name: Deploy Database Migrations runs-on: ubuntu-latest + environment: production steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v1 + - name: Setup Bun + uses: oven-sh/setup-bun@v2 with: - bun-version: '1.3.2' + bun-version: 1.3.3 - name: Cache dependencies uses: actions/cache@v4 with: path: ~/.bun/install/cache - key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} - name: Install dependencies - run: bun install --frozen-lockfile + run: bun install --frozen - name: Run database migrations + working-directory: packages/db env: DATABASE_URL: ${{ secrets.DATABASE_URL_UNPOOLED }} + DATABASE_URL_UNPOOLED: ${{ secrets.DATABASE_URL_UNPOOLED }} + run: bun drizzle-kit migrate + + deploy-api: + name: Deploy API to Vercel + runs-on: ubuntu-latest + environment: production + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy API + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_API_PROJECT_ID }} + NEXT_PUBLIC_WEB_URL: ${{ secrets.NEXT_PUBLIC_WEB_URL }} + NEXT_PUBLIC_ADMIN_URL: ${{ secrets.NEXT_PUBLIC_ADMIN_URL }} + MOCK_USER_ID: ${{ secrets.MOCK_USER_ID }} + run: | + vercel pull --yes --environment=production --token=$VERCEL_TOKEN + vercel build --prod --token=$VERCEL_TOKEN + vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN + + deploy-web: + name: Deploy Web to Vercel + runs-on: ubuntu-latest + environment: production + needs: deploy-api + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy Web + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_WEB_PROJECT_ID }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + DATABASE_URL_UNPOOLED: ${{ secrets.DATABASE_URL_UNPOOLED }} + NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} + NEXT_PUBLIC_MARKETING_URL: ${{ secrets.NEXT_PUBLIC_MARKETING_URL }} + NEXT_PUBLIC_DOCS_URL: ${{ secrets.NEXT_PUBLIC_DOCS_URL }} + MOCK_USER_ID: ${{ secrets.MOCK_USER_ID }} + run: | + vercel pull --yes --environment=production --token=$VERCEL_TOKEN + vercel build --prod --token=$VERCEL_TOKEN + vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN + + deploy-marketing: + name: Deploy Marketing to Vercel + runs-on: ubuntu-latest + environment: production + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy Marketing + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_MARKETING_PROJECT_ID }} + NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} + run: | + vercel pull --yes --environment=production --token=$VERCEL_TOKEN + vercel build --prod --token=$VERCEL_TOKEN + vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN + + deploy-admin: + name: Deploy Admin to Vercel + runs-on: ubuntu-latest + environment: production + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy Admin + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_ADMIN_PROJECT_ID }} + DATABASE_URL: ${{ secrets.DATABASE_URL }} + DATABASE_URL_UNPOOLED: ${{ secrets.DATABASE_URL_UNPOOLED }} + NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} + NEXT_PUBLIC_WEB_URL: ${{ secrets.NEXT_PUBLIC_WEB_URL }} + MOCK_USER_ID: ${{ secrets.MOCK_USER_ID }} + run: | + vercel pull --yes --environment=production --token=$VERCEL_TOKEN + vercel build --prod --token=$VERCEL_TOKEN + vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN + + deploy-docs: + name: Deploy Docs to Vercel + runs-on: ubuntu-latest + environment: production + needs: deploy-database + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Install Vercel CLI + run: npm install --global vercel@latest + + - name: Build and Deploy Docs + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_DOCS_PROJECT_ID }} + NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }} + NEXT_PUBLIC_WEB_URL: ${{ secrets.NEXT_PUBLIC_WEB_URL }} + NEXT_PUBLIC_MARKETING_URL: ${{ secrets.NEXT_PUBLIC_MARKETING_URL }} run: | - cd packages/db - bunx drizzle-kit migrate + vercel pull --yes --environment=production --token=$VERCEL_TOKEN + vercel build --prod --token=$VERCEL_TOKEN + vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN diff --git a/apps/admin/src/env.ts b/apps/admin/src/env.ts index 02472099543..d24b66775de 100644 --- a/apps/admin/src/env.ts +++ b/apps/admin/src/env.ts @@ -18,7 +18,7 @@ export const env = createEnv({ client: { NEXT_PUBLIC_API_URL: z.string().url(), - NEXT_PUBLIC_WEB_URL: z.string().url().optional(), + NEXT_PUBLIC_WEB_URL: z.string().url(), }, experimental__runtimeEnv: { diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts index 090ad9bbbc2..23f37b19c63 100644 --- a/apps/api/src/env.ts +++ b/apps/api/src/env.ts @@ -8,7 +8,7 @@ export const env = createEnv({ }, client: { NEXT_PUBLIC_WEB_URL: z.string().url(), - NEXT_PUBLIC_ADMIN_URL: z.string().url().optional(), + NEXT_PUBLIC_ADMIN_URL: z.string().url(), }, experimental__runtimeEnv: { NEXT_PUBLIC_WEB_URL: process.env.NEXT_PUBLIC_WEB_URL, diff --git a/apps/marketing/src/env.ts b/apps/marketing/src/env.ts index d628bc6f5b7..154576f6fb0 100644 --- a/apps/marketing/src/env.ts +++ b/apps/marketing/src/env.ts @@ -11,7 +11,7 @@ export const env = createEnv({ }, server: {}, client: { - NEXT_PUBLIC_API_URL: z.string().url().optional(), + NEXT_PUBLIC_API_URL: z.string().url(), }, experimental__runtimeEnv: { NODE_ENV: process.env.NODE_ENV, diff --git a/apps/web/src/env.ts b/apps/web/src/env.ts index a8eb4893662..0f392e34f12 100644 --- a/apps/web/src/env.ts +++ b/apps/web/src/env.ts @@ -18,8 +18,8 @@ export const env = createEnv({ client: { NEXT_PUBLIC_API_URL: z.string().url(), - NEXT_PUBLIC_MARKETING_URL: z.string().url().optional(), - NEXT_PUBLIC_DOCS_URL: z.string().url().optional(), + NEXT_PUBLIC_MARKETING_URL: z.string().url(), + NEXT_PUBLIC_DOCS_URL: z.string().url(), }, experimental__runtimeEnv: { diff --git a/packages/trpc/src/env.ts b/packages/trpc/src/env.ts index f049168f27c..31d24ce54ca 100644 --- a/packages/trpc/src/env.ts +++ b/packages/trpc/src/env.ts @@ -3,7 +3,7 @@ import { z } from "zod"; export const env = createEnv({ server: { - MOCK_USER_ID: z.string().uuid().optional(), + MOCK_USER_ID: z.string().uuid(), }, clientPrefix: "PUBLIC_", client: {}, From 8744819fe4c667245dcfa8873d0dd0f9fd54095a Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Fri, 12 Dec 2025 00:09:35 -0500 Subject: [PATCH 4/5] WIP --- .envrc | 1 - .gitignore | 1 + .superset/setup.sh | 10 +++++----- 3 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 .envrc diff --git a/.envrc b/.envrc deleted file mode 100644 index fe7c01aa90e..00000000000 --- a/.envrc +++ /dev/null @@ -1 +0,0 @@ -dotenv diff --git a/.gitignore b/.gitignore index 3131b454638..7383c977d07 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ yarn.lock # Env variables .env .env.* +.envrc mise.toml diff --git a/.superset/setup.sh b/.superset/setup.sh index 1badfdf0f24..863f3d03999 100755 --- a/.superset/setup.sh +++ b/.superset/setup.sh @@ -44,17 +44,17 @@ NEON_OUTPUT=$(neonctl branches create \ # Parse connection strings from create output BRANCH_ID=$(echo "$NEON_OUTPUT" | jq -r '.branch.id') -DATABASE_URL=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_uri') +DIRECT_URL=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_uri') POOLER_HOST=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_parameters.pooler_host') PASSWORD=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_parameters.password') ROLE=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_parameters.role') DATABASE=$(echo "$NEON_OUTPUT" | jq -r '.connection_uris[0].connection_parameters.database') -DATABASE_POOLED_URL="postgresql://${ROLE}:${PASSWORD}@${POOLER_HOST}/${DATABASE}?sslmode=require" +POOLED_URL="postgresql://${ROLE}:${PASSWORD}@${POOLER_HOST}/${DATABASE}?sslmode=require" -cat > .env << EOF +cat >> .env << EOF NEON_BRANCH_ID=$BRANCH_ID -DATABASE_URL=$DATABASE_URL -DATABASE_POOLED_URL=$DATABASE_POOLED_URL +DATABASE_URL=$POOLED_URL +DATABASE_URL_UNPOOLED=$DIRECT_URL EOF success "Neon branch created: $WORKSPACE_NAME" From 65a1418c285958706fd54478672f9b8093e3550d Mon Sep 17 00:00:00 2001 From: Satya Patel Date: Fri, 12 Dec 2025 00:31:57 -0500 Subject: [PATCH 5/5] ci: add build job for CLI and Desktop apps --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e381389c701..8c9dac5281e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,3 +78,27 @@ jobs: - name: Typecheck run: bun run typecheck + + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.3 + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }} + + - name: Install dependencies + run: bun install --frozen + + - name: Build CLI and Desktop + run: bun turbo run build --filter=@superset/cli --filter=@superset/desktop