diff --git a/apps/swc-plugins/.eslintignore b/apps/swc-plugins/.eslintignore
new file mode 100644
index 0000000..7eaeda2
--- /dev/null
+++ b/apps/swc-plugins/.eslintignore
@@ -0,0 +1,3 @@
+*.d.ts
+/lib/generated
+/components/ui
diff --git a/apps/swc-plugins/.eslintrc.json b/apps/swc-plugins/.eslintrc.json
new file mode 100644
index 0000000..4ea7446
--- /dev/null
+++ b/apps/swc-plugins/.eslintrc.json
@@ -0,0 +1,8 @@
+{
+ "extends": "next/core-web-vitals",
+ "rules": {
+ "@typescript-eslint/no-unused-vars": "off",
+ "@next/next/no-server-import-in-page": "off",
+ "@typescript-eslint/ban-types": "off"
+ }
+}
diff --git a/apps/swc-plugins/.gitignore b/apps/swc-plugins/.gitignore
new file mode 100644
index 0000000..7b18c1c
--- /dev/null
+++ b/apps/swc-plugins/.gitignore
@@ -0,0 +1,39 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# scripts
+.cache/
\ No newline at end of file
diff --git a/apps/swc-plugins/README.md b/apps/swc-plugins/README.md
new file mode 100644
index 0000000..5ce4a7c
--- /dev/null
+++ b/apps/swc-plugins/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/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/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## 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/deployment) for more details.
diff --git a/apps/swc-plugins/app/(home)/page.tsx b/apps/swc-plugins/app/(home)/page.tsx
new file mode 100644
index 0000000..0129517
--- /dev/null
+++ b/apps/swc-plugins/app/(home)/page.tsx
@@ -0,0 +1,33 @@
+import { Logo } from "@/components/logo";
+import { RuntimeVersionSelector } from "@/components/runtime-version-selector";
+import { Button } from "@/components/ui/button";
+import { Metadata } from "next";
+import Link from "next/link";
+import { FC } from "react";
+
+export const metadata: Metadata = {
+ title: "SWC Plugins",
+ description: "A collection of SWC plugins, ready to use in your project.",
+};
+
+const Home: FC = () => (
+
+
+
+
+
+ SWC Plugins
+
+
+ A collection of SWC plugins, ready to use in your project.
+
+
+
+
+ or see all versions
+
+
+
+);
+
+export default Home;
diff --git a/apps/swc-plugins/app/api-client-provider.tsx b/apps/swc-plugins/app/api-client-provider.tsx
new file mode 100644
index 0000000..5980269
--- /dev/null
+++ b/apps/swc-plugins/app/api-client-provider.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { apiClient } from "@/lib/trpc/web-client";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { httpBatchLink } from "@trpc/client";
+import { PropsWithChildren, useState } from "react";
+import superjson from "superjson";
+
+export function ApiClientProvider({ children }: PropsWithChildren<{}>) {
+ const [queryClient] = useState(
+ () =>
+ new QueryClient({
+ defaultOptions: {
+ queries: {
+ queryKeyHashFn: (queryKey) =>
+ superjson.stringify(queryKey),
+ },
+ },
+ })
+ );
+ const [trpcClient] = useState(() =>
+ apiClient.createClient({
+ links: [
+ httpBatchLink({
+ url: "/api/trpc",
+ transformer: superjson,
+ }),
+ ],
+ })
+ );
+ return (
+
+
+ {children}
+
+
+ );
+}
diff --git a/apps/swc-plugins/app/api/auth/[...nextauth]/route.ts b/apps/swc-plugins/app/api/auth/[...nextauth]/route.ts
new file mode 100644
index 0000000..73228a0
--- /dev/null
+++ b/apps/swc-plugins/app/api/auth/[...nextauth]/route.ts
@@ -0,0 +1,2 @@
+import { handlers } from "@/lib/auth";
+export const { GET, POST } = handlers;
diff --git a/apps/swc-plugins/app/api/trpc/[trpc]/route.ts b/apps/swc-plugins/app/api/trpc/[trpc]/route.ts
new file mode 100644
index 0000000..09bbacc
--- /dev/null
+++ b/apps/swc-plugins/app/api/trpc/[trpc]/route.ts
@@ -0,0 +1,3 @@
+import { handler } from "@/lib/api/server";
+
+export { handler as GET, handler as POST };
diff --git a/apps/swc-plugins/app/api/update/runtimes/route.ts b/apps/swc-plugins/app/api/update/runtimes/route.ts
new file mode 100644
index 0000000..ad3c950
--- /dev/null
+++ b/apps/swc-plugins/app/api/update/runtimes/route.ts
@@ -0,0 +1,13 @@
+import { UpdateRuntimesInputSchema } from "@/lib/api/updater/router";
+import { createCaller } from "@/lib/server";
+import { NextRequest, NextResponse } from "next/server";
+
+export const POST = async (req: NextRequest) => {
+ const body = UpdateRuntimesInputSchema.parse(await req.json());
+
+ const api = await createCaller();
+
+ await api.updater.updateRuntimes(body);
+
+ return NextResponse.json({ ok: true });
+};
diff --git a/apps/swc-plugins/app/api/update/wasm-plugins/route.ts b/apps/swc-plugins/app/api/update/wasm-plugins/route.ts
new file mode 100644
index 0000000..c0e5fa1
--- /dev/null
+++ b/apps/swc-plugins/app/api/update/wasm-plugins/route.ts
@@ -0,0 +1,13 @@
+import { UpdateWasmPluginsInputSchema } from "@/lib/api/updater/router";
+import { createCaller } from "@/lib/server";
+import { NextRequest, NextResponse } from "next/server";
+
+export const POST = async (req: NextRequest) => {
+ const body = UpdateWasmPluginsInputSchema.parse(await req.json());
+
+ const api = await createCaller();
+
+ await api.updater.updateWasmPlugins(body);
+
+ return NextResponse.json({ ok: true });
+};
diff --git a/apps/swc-plugins/app/client-providers.tsx b/apps/swc-plugins/app/client-providers.tsx
new file mode 100644
index 0000000..578a672
--- /dev/null
+++ b/apps/swc-plugins/app/client-providers.tsx
@@ -0,0 +1,13 @@
+"use client";
+
+import { ThemeProvider } from "next-themes";
+import { PropsWithChildren } from "react";
+import { ApiClientProvider } from "./api-client-provider";
+
+export function ClientProviders({ children }: PropsWithChildren) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/swc-plugins/app/favicon.ico b/apps/swc-plugins/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/apps/swc-plugins/app/favicon.ico differ
diff --git a/apps/swc-plugins/app/globals.css b/apps/swc-plugins/app/globals.css
new file mode 100644
index 0000000..61399ce
--- /dev/null
+++ b/apps/swc-plugins/app/globals.css
@@ -0,0 +1,69 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+ --primary: 222.2 47.4% 11.2%;
+ --primary-foreground: 210 40% 98%;
+ --secondary: 210 40% 96.1%;
+ --secondary-foreground: 222.2 47.4% 11.2%;
+ --muted: 210 40% 96.1%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+ --accent: 210 40% 96.1%;
+ --accent-foreground: 222.2 47.4% 11.2%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+ --ring: 222.2 84% 4.9%;
+ --radius: 0.5rem;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ }
+
+ .dark {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+ --primary: 210 40% 98%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+ --ring: 212.7 26.8% 83.9%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/apps/swc-plugins/app/import/ranges/page.tsx b/apps/swc-plugins/app/import/ranges/page.tsx
new file mode 100644
index 0000000..4e31f4c
--- /dev/null
+++ b/apps/swc-plugins/app/import/ranges/page.tsx
@@ -0,0 +1,46 @@
+import { db } from "@/lib/prisma";
+import fs from "node:fs/promises";
+
+export default async function Page() {
+ if (process.env.NODE_ENV === "production") {
+ return
Not allowed
;
+ }
+
+ const ranges: { min: string; max: string }[] = JSON.parse(
+ await fs.readFile("./data/ranges.json", "utf8")
+ );
+
+ for (const { min, max } of ranges) {
+ await db.compatRange.upsert({
+ where: {
+ from_to: {
+ from: min,
+ to: max,
+ },
+ },
+ update: {},
+ create: {
+ from: min,
+ to: max,
+ },
+ });
+ }
+
+ const runtimes = ["@swc/core", "next", "rspack", "farm"];
+
+ for (const runtime of runtimes) {
+ await db.swcRuntime.upsert({
+ where: {
+ name: runtime,
+ },
+ update: {},
+ create: {
+ name: runtime,
+ },
+ });
+ }
+
+ return Done
;
+}
+
+export const dynamic = "force-dynamic";
diff --git a/apps/swc-plugins/app/import/runtime/route.ts b/apps/swc-plugins/app/import/runtime/route.ts
new file mode 100644
index 0000000..c3763d7
--- /dev/null
+++ b/apps/swc-plugins/app/import/runtime/route.ts
@@ -0,0 +1,76 @@
+import { db } from "@/lib/prisma";
+import { createCaller } from "@/lib/server";
+import { NextRequest, NextResponse } from "next/server";
+import { z } from "zod";
+
+const VersionSchema = z.object({
+ version: z.string(),
+ swcCoreVersion: z.string(),
+});
+
+const BodySchema = z.object({
+ runtime: z.enum(["@swc/core", "next", "rspack"]),
+ versions: z.array(VersionSchema),
+});
+
+export async function POST(req: NextRequest) {
+ if (process.env.NODE_ENV === "production") {
+ return NextResponse.json(
+ {
+ error: "Not allowed",
+ },
+ {
+ status: 403,
+ }
+ );
+ }
+
+ const { runtime, versions } = BodySchema.parse(await req.json());
+
+ const rt = await db.swcRuntime.findUniqueOrThrow({
+ where: {
+ name: runtime,
+ },
+ });
+ const api = await createCaller();
+
+ for (const version of versions) {
+ const compatRange = await api.compatRange.byCoreVersion({
+ version: version.swcCoreVersion,
+ });
+ if (!compatRange) {
+ console.log(`No compat range found for ${version.swcCoreVersion}`);
+ continue;
+ }
+
+ try {
+ await db.swcRuntimeVersion.upsert({
+ where: {
+ runtimeId_version: {
+ runtimeId: rt.id,
+ version: version.version.replace("v", ""),
+ },
+ },
+ update: {
+ compatRangeId: compatRange.id,
+ swcCoreVersion: version.swcCoreVersion.replace("v", ""),
+ },
+ create: {
+ runtimeId: rt.id,
+ version: version.version.replace("v", ""),
+ compatRangeId: compatRange.id,
+ swcCoreVersion: version.swcCoreVersion.replace("v", ""),
+ },
+ });
+ } catch (e) {
+ console.error(
+ `Failed to create compat range for ${version.swcCoreVersion}: ${e}`
+ );
+ continue;
+ }
+ }
+
+ return NextResponse.json({
+ ok: true,
+ });
+}
diff --git a/apps/swc-plugins/app/import/swc_core/route.ts b/apps/swc-plugins/app/import/swc_core/route.ts
new file mode 100644
index 0000000..1943106
--- /dev/null
+++ b/apps/swc-plugins/app/import/swc_core/route.ts
@@ -0,0 +1,29 @@
+import { createCaller } from "@/lib/server";
+import { NextRequest, NextResponse } from "next/server";
+import { z } from "zod";
+
+const CoreVersionSchema = z.object({
+ version: z.string(),
+ pluginRunnerReq: z.string(),
+});
+
+const BodySchema = z.object({
+ coreVersions: z.array(CoreVersionSchema),
+ pluginRunnerVersions: z.array(z.string()),
+});
+
+export async function POST(req: NextRequest) {
+ const api = await createCaller();
+ const { coreVersions, pluginRunnerVersions } = BodySchema.parse(
+ await req.json()
+ );
+
+ await api.compatRange.addCacheForCrates({
+ coreVersions,
+ pluginRunnerVersions,
+ });
+
+ return NextResponse.json({
+ ok: true,
+ });
+}
diff --git a/apps/swc-plugins/app/layout.tsx b/apps/swc-plugins/app/layout.tsx
new file mode 100644
index 0000000..c1649ea
--- /dev/null
+++ b/apps/swc-plugins/app/layout.tsx
@@ -0,0 +1,31 @@
+import { Dynamic } from "@/components/dynamic";
+import { Toaster } from "@/components/ui/toaster";
+import { fontBody, fontHeading } from "@/lib/fonts";
+import { cn } from "@/lib/utils";
+import { SessionProvider } from "next-auth/react";
+import NextTopLoader from "nextjs-toploader";
+import { FC, PropsWithChildren } from "react";
+import { ClientProviders } from "./client-providers";
+import "./globals.css";
+
+const RootLayout: FC = ({ children }) => (
+
+
+
+
+
+ {children}
+
+
+
+
+
+);
+
+export default RootLayout;
diff --git a/apps/swc-plugins/app/loading.tsx b/apps/swc-plugins/app/loading.tsx
new file mode 100644
index 0000000..10e0561
--- /dev/null
+++ b/apps/swc-plugins/app/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/swc-plugins/app/robots.ts b/apps/swc-plugins/app/robots.ts
new file mode 100644
index 0000000..ace613c
--- /dev/null
+++ b/apps/swc-plugins/app/robots.ts
@@ -0,0 +1,11 @@
+import { MetadataRoute } from "next";
+
+export default function robots(): MetadataRoute.Robots {
+ return {
+ rules: {
+ userAgent: "*",
+ allow: "/",
+ },
+ // sitemap: `${BASE_URL}/sitemap.xml`,
+ };
+}
diff --git a/apps/swc-plugins/app/versions/from-core/[version]/page.tsx b/apps/swc-plugins/app/versions/from-core/[version]/page.tsx
new file mode 100644
index 0000000..4e070f0
--- /dev/null
+++ b/apps/swc-plugins/app/versions/from-core/[version]/page.tsx
@@ -0,0 +1,19 @@
+import { createCaller } from "@/lib/server";
+import { redirect } from "next/navigation";
+
+export default async function Page({
+ params: { version },
+}: {
+ params: { version: string };
+}) {
+ const api = await createCaller();
+ const compatRange = await api.compatRange.byCoreVersion({
+ version,
+ });
+
+ if (compatRange) {
+ return redirect(`/compat/range/${compatRange.id}`);
+ }
+
+ return No compat range found for swc_core@{version}
;
+}
diff --git a/apps/swc-plugins/app/versions/from-plugin-runner/[version]/page.tsx b/apps/swc-plugins/app/versions/from-plugin-runner/[version]/page.tsx
new file mode 100644
index 0000000..340f69b
--- /dev/null
+++ b/apps/swc-plugins/app/versions/from-plugin-runner/[version]/page.tsx
@@ -0,0 +1,19 @@
+import { createCaller } from "@/lib/server";
+import { redirect } from "next/navigation";
+
+export default async function Page({
+ params: { version },
+}: {
+ params: { version: string };
+}) {
+ const api = await createCaller();
+ const compatRange = await api.compatRange.byPluginRunnerVersion({
+ version,
+ });
+
+ if (compatRange) {
+ return redirect(`/compat/range/${compatRange.id}`);
+ }
+
+ return No compat range found for swc_plugin_runner@{version}
;
+}
diff --git a/apps/swc-plugins/app/versions/layout.tsx b/apps/swc-plugins/app/versions/layout.tsx
new file mode 100644
index 0000000..9b6370c
--- /dev/null
+++ b/apps/swc-plugins/app/versions/layout.tsx
@@ -0,0 +1,28 @@
+import Link from "next/link";
+import { FC, ReactNode } from "react";
+import { Logo } from "../../components/logo";
+import { RuntimeVersionSelector } from "../../components/runtime-version-selector";
+
+type ResultsLayoutProps = {
+ children: ReactNode;
+};
+
+const ResultsLayout: FC = ({ children }) => {
+ return (
+ <>
+
+
+
+ {children}
+ >
+ );
+};
+
+export default ResultsLayout;
diff --git a/apps/swc-plugins/app/versions/range/[compatRangeId]/components/compat-range-header.tsx b/apps/swc-plugins/app/versions/range/[compatRangeId]/components/compat-range-header.tsx
new file mode 100644
index 0000000..873872c
--- /dev/null
+++ b/apps/swc-plugins/app/versions/range/[compatRangeId]/components/compat-range-header.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import { Checkbox } from "@/components/ui/checkbox";
+import { apiClient } from "@/lib/trpc/web-client";
+import { parseAsBoolean, useQueryState } from "next-usequerystate";
+import { FC } from "react";
+
+type CompatRangeHeaderProps = {
+ compatRangeId: string;
+};
+
+export const CompatRangeHeader: FC = ({
+ compatRangeId,
+}) => {
+ const [includePrerelease, setIncludePrerelease] = useQueryState(
+ "includePrerelease",
+ parseAsBoolean.withDefault(false)
+ );
+ const [compatRange] = apiClient.compatRange.get.useSuspenseQuery({
+ id: BigInt(compatRangeId),
+ includePrerelease,
+ });
+
+ const handleCheckedChange = (checked: boolean) => {
+ setIncludePrerelease(checked);
+ };
+
+ return (
+
+
+ swc_core
+
+ @{compatRange.from} -{" "}
+ {compatRange.to}
+
+
+
+
+
+ Include Prerelease
+
+
+ );
+};
diff --git a/apps/swc-plugins/app/versions/range/[compatRangeId]/components/compat-range-tables.tsx b/apps/swc-plugins/app/versions/range/[compatRangeId]/components/compat-range-tables.tsx
new file mode 100644
index 0000000..a4d2d6b
--- /dev/null
+++ b/apps/swc-plugins/app/versions/range/[compatRangeId]/components/compat-range-tables.tsx
@@ -0,0 +1,97 @@
+"use client";
+
+import { TableContainer } from "@/components/table-container";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { apiClient } from "@/lib/trpc/web-client";
+import { parseAsBoolean, useQueryState } from "next-usequerystate";
+import { FC } from "react";
+
+type CompatRangeTablesProps = {
+ compatRangeId: string;
+};
+
+export const CompatRangeTables: FC = ({
+ compatRangeId,
+}) => {
+ const [includePrerelease] = useQueryState(
+ "includePrerelease",
+ parseAsBoolean.withDefault(false)
+ );
+ const [compatRange] = apiClient.compatRange.get.useSuspenseQuery({
+ id: BigInt(compatRangeId),
+ includePrerelease,
+ });
+
+ return (
+ <>
+
+
+
+
+ Runtime
+
+ Minimum Version
+
+
+ Maximum Version
+
+
+
+
+ {compatRange.runtimes.map((runtime) => (
+
+
+ {runtime.name}
+
+
+ {runtime.minVersion}
+
+
+ {runtime.maxVersion}
+
+
+ ))}
+
+
+
+
+
+
+
+
+ Plugin
+
+ Minimum Version
+
+
+ Maximum Version
+
+
+
+
+ {compatRange.plugins.map((plugin) => (
+
+
+ {plugin.name}
+
+
+ {plugin.minVersion}
+
+
+ {plugin.maxVersion}
+
+
+ ))}
+
+
+
+ >
+ );
+};
diff --git a/apps/swc-plugins/app/versions/range/[compatRangeId]/page.tsx b/apps/swc-plugins/app/versions/range/[compatRangeId]/page.tsx
new file mode 100644
index 0000000..9aa1be0
--- /dev/null
+++ b/apps/swc-plugins/app/versions/range/[compatRangeId]/page.tsx
@@ -0,0 +1,27 @@
+import { Metadata } from "next";
+import { FC } from "react";
+import { CompatRangeHeader } from "./components/compat-range-header";
+import { CompatRangeTables } from "./components/compat-range-tables";
+
+export const dynamic = "force-dynamic";
+export const metadata: Metadata = {
+ title: "Compat Range",
+ description: "Compat ranges for swc_core",
+};
+
+type CompatRangePageProps = {
+ params: {
+ compatRangeId: string;
+ };
+};
+
+const CompatRangePage: FC = ({
+ params: { compatRangeId },
+}) => (
+
+
+
+
+);
+
+export default CompatRangePage;
diff --git a/apps/swc-plugins/app/versions/range/components/range-table.tsx b/apps/swc-plugins/app/versions/range/components/range-table.tsx
new file mode 100644
index 0000000..3490a48
--- /dev/null
+++ b/apps/swc-plugins/app/versions/range/components/range-table.tsx
@@ -0,0 +1,61 @@
+"use client";
+
+import { TableContainer } from "@/components/table-container";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { useRouter } from "next/navigation";
+
+type RangeTableProps = {
+ ranges: { id: bigint; from: string; to: string }[];
+};
+
+export const RangeTable = ({ ranges }: RangeTableProps) => {
+ const router = useRouter();
+
+ const handleClick = (id: bigint) => {
+ router.push(`/versions/range/${id}`);
+ };
+
+ return (
+
+
+
+
+ Runtime
+
+ Minimum Version
+
+
+ Maximum Version
+
+
+
+
+ {ranges.map((range) => (
+ handleClick(range.id)}
+ >
+
+ swc_core
+
+
+ {range.from}
+
+
+ {range.to}
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/apps/swc-plugins/app/versions/range/page.tsx b/apps/swc-plugins/app/versions/range/page.tsx
new file mode 100644
index 0000000..af7627f
--- /dev/null
+++ b/apps/swc-plugins/app/versions/range/page.tsx
@@ -0,0 +1,19 @@
+import { createCaller } from "@/lib/server";
+import { Metadata } from "next";
+import { RangeTable } from "./components/range-table";
+
+export const dynamic = "force-dynamic";
+export const fetchCache = "force-no-store";
+export const metadata: Metadata = {
+ title: "Compat Ranges",
+ description: "A list of compat ranges for SWC plugins.",
+};
+
+const RangePage = async () => {
+ const api = await createCaller();
+ const ranges = await api.compatRange.list();
+
+ return ;
+};
+
+export default RangePage;
diff --git a/apps/swc-plugins/components.json b/apps/swc-plugins/components.json
new file mode 100644
index 0000000..835bc87
--- /dev/null
+++ b/apps/swc-plugins/components.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "app/globals.css",
+ "baseColor": "slate",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils"
+ }
+}
diff --git a/apps/swc-plugins/components/dynamic.tsx b/apps/swc-plugins/components/dynamic.tsx
new file mode 100644
index 0000000..a3c1832
--- /dev/null
+++ b/apps/swc-plugins/components/dynamic.tsx
@@ -0,0 +1,17 @@
+"use client";
+
+import { ReactNode, useEffect, useState } from "react";
+
+export const Dynamic = ({ children }: { children: ReactNode }) => {
+ const [hasMounted, setHasMounted] = useState(false);
+
+ useEffect(() => {
+ setHasMounted(true);
+ }, []);
+
+ if (!hasMounted) {
+ return null;
+ }
+
+ return <>{children} >;
+};
diff --git a/apps/swc-plugins/components/logo/index.tsx b/apps/swc-plugins/components/logo/index.tsx
new file mode 100644
index 0000000..102fea6
--- /dev/null
+++ b/apps/swc-plugins/components/logo/index.tsx
@@ -0,0 +1,15 @@
+import { FC } from "react";
+
+import { cn } from "@/lib/utils";
+import Image from "next/image";
+import SWCLogo from "./swc.svg";
+
+export const Logo: FC<{ className?: string }> = ({ className }) => (
+
+);
diff --git a/apps/swc-plugins/components/logo/swc.svg b/apps/swc-plugins/components/logo/swc.svg
new file mode 100644
index 0000000..b0ca9e3
--- /dev/null
+++ b/apps/swc-plugins/components/logo/swc.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/swc-plugins/components/runtime-version-selector.tsx b/apps/swc-plugins/components/runtime-version-selector.tsx
new file mode 100644
index 0000000..272fffd
--- /dev/null
+++ b/apps/swc-plugins/components/runtime-version-selector.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import { apiClient } from "@/lib/trpc/web-client";
+import { useRouter } from "next/navigation";
+import { FC, useState } from "react";
+import { Select } from "./select";
+
+export const RuntimeVersionSelector: FC = () => {
+ const [runtimes] = apiClient.runtime.list.useSuspenseQuery();
+ const [selectedRuntime, setSelectedRuntime] = useState();
+ const [selectedVersion, setSelectedVersion] = useState();
+ const router = useRouter();
+ const versions = apiClient.runtime.listVersions.useQuery({
+ runtimeId: selectedRuntime ?? BigInt(0),
+ });
+
+ const handleRuntimeChange = (runtimeId: string) => {
+ setSelectedRuntime(BigInt(runtimeId));
+ };
+
+ const handleVersionChange = (version: string) => {
+ const selected = versions.data?.find((v) => v.version === version);
+ setSelectedVersion(version);
+ router.push(`/versions/range/${selected?.compatRangeId}`);
+ };
+
+ return (
+
+ ({
+ value: runtime.id.toString(),
+ label: runtime.name,
+ }))}
+ onChange={handleRuntimeChange}
+ type="runtime"
+ />
+ ({
+ value: version.version,
+ label: version.version,
+ })) ?? []
+ }
+ type="version"
+ />
+
+ );
+};
diff --git a/apps/swc-plugins/components/select.tsx b/apps/swc-plugins/components/select.tsx
new file mode 100644
index 0000000..2090da4
--- /dev/null
+++ b/apps/swc-plugins/components/select.tsx
@@ -0,0 +1,196 @@
+"use client";
+
+import { useMeasure } from "@react-hookz/web";
+import { useCommandState } from "cmdk";
+import { CheckIcon, ChevronsUpDown, PlusIcon } from "lucide-react";
+import type { ComponentProps, FC, ReactNode } from "react";
+import { useCallback, useId, useState } from "react";
+import { cn } from "../lib/utils";
+import { Button } from "./ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "./ui/command";
+import { Label } from "./ui/label";
+import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
+
+type SelectProperties = Omit<
+ ComponentProps,
+ "open" | "setOpen"
+> & {
+ readonly label?: string;
+ readonly caption?: string;
+ readonly value?: string[] | string;
+ readonly data: readonly {
+ readonly value: string;
+ readonly label: string;
+ }[];
+ readonly renderItem?: (item: SelectProperties["data"][number]) => ReactNode;
+ readonly disabled?: boolean;
+ readonly type?: string;
+ readonly trigger?: ReactNode;
+ readonly onChange?: (value: string) => void;
+ readonly onCreate?: (value: string) => void;
+ readonly loading?: boolean;
+ readonly exactSearch?: boolean;
+ readonly className?: string;
+};
+
+const CreateEmptyState = ({
+ onCreate,
+}: {
+ readonly onCreate: SelectProperties["onCreate"];
+}) => {
+ const search = useCommandState((state) => state.search);
+
+ return (
+
+
onCreate?.(search)}
+ type="button"
+ className="flex items-center gap-2"
+ >
+
+ Create "{search}"
+
+
+ );
+};
+
+export const Select: FC = ({
+ label,
+ value,
+ caption,
+ data,
+ disabled,
+ onChange,
+ type = "item",
+ renderItem,
+ trigger,
+ loading,
+ onCreate,
+ exactSearch,
+ className,
+ ...properties
+}) => {
+ const id = useId();
+ const [open, setOpen] = useState(false);
+ const selected = data.find((item) => item.value === value);
+ const [measurements, ref] = useMeasure();
+
+ const handleSelect = useCallback(
+ (newValue: string) => {
+ setOpen(false);
+ onChange?.(newValue);
+ },
+ [onChange]
+ );
+
+ const handleCreate = (newValue: string) => {
+ setOpen(false);
+ onCreate?.(newValue);
+ };
+
+ return (
+
+ disabled ? setOpen(false) : setOpen(newOpen)
+ }
+ >
+
+ {trigger ?? (
+
+ {label ?
{label} : null}
+
+ {!selected && `Select a ${type}...`}
+ {selected && !renderItem ? selected.label : null}
+ {selected && renderItem ? (
+
+ {renderItem(selected)}
+
+ ) : null}
+ {loading ? (
+ Loading...
+ ) : (
+
+ )}
+
+ {caption ? (
+
+ {caption}
+
+ ) : null}
+
+ )}
+
+
+
+
+
+ {onCreate ? (
+
+
+
+ ) : (
+ No results found.
+ )}
+
+ {data.map((item) => {
+ const active = Array.isArray(value)
+ ? value.includes(item.value)
+ : value === item.value;
+
+ return (
+
+ handleSelect(item.value)
+ }
+ className="flex items-center gap-2"
+ >
+
+ {renderItem
+ ? renderItem(item)
+ : item.label}
+
+
+
+ );
+ })}
+
+
+
+
+
+ );
+};
diff --git a/apps/swc-plugins/components/table-container.tsx b/apps/swc-plugins/components/table-container.tsx
new file mode 100644
index 0000000..fc38f6c
--- /dev/null
+++ b/apps/swc-plugins/components/table-container.tsx
@@ -0,0 +1,29 @@
+import { cn } from "@/lib/utils";
+import type { FC, ReactNode } from "react";
+
+type TableContainerProperties = {
+ readonly title: string;
+ readonly children: ReactNode;
+ readonly className?: string;
+};
+
+export const TableContainer: FC = ({
+ title,
+ children,
+ className,
+}) => (
+
+
+ {title}
+
+
+ {children}
+
+
+);
diff --git a/apps/swc-plugins/components/ui/accordion.tsx b/apps/swc-plugins/components/ui/accordion.tsx
new file mode 100644
index 0000000..b1e2db3
--- /dev/null
+++ b/apps/swc-plugins/components/ui/accordion.tsx
@@ -0,0 +1,58 @@
+"use client";
+
+import * as React from "react";
+import * as AccordionPrimitive from "@radix-ui/react-accordion";
+import { ChevronDown } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+const Accordion = AccordionPrimitive.Root;
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AccordionItem.displayName = "AccordionItem";
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+));
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+));
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName;
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
diff --git a/apps/swc-plugins/components/ui/alert-dialog.tsx b/apps/swc-plugins/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..e9799b5
--- /dev/null
+++ b/apps/swc-plugins/components/ui/alert-dialog.tsx
@@ -0,0 +1,141 @@
+"use client";
+
+import * as React from "react";
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
+
+const AlertDialog = AlertDialogPrimitive.Root;
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal;
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+));
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+AlertDialogHeader.displayName = "AlertDialogHeader";
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+AlertDialogFooter.displayName = "AlertDialogFooter";
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName;
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+};
diff --git a/apps/swc-plugins/components/ui/alert.tsx b/apps/swc-plugins/components/ui/alert.tsx
new file mode 100644
index 0000000..2d6922c
--- /dev/null
+++ b/apps/swc-plugins/components/ui/alert.tsx
@@ -0,0 +1,62 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const alertVariants = cva(
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+ {
+ variants: {
+ variant: {
+ default: "bg-background text-foreground",
+ destructive:
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+);
+
+const Alert = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & VariantProps
+>(({ className, variant, ...props }, ref) => (
+
+));
+Alert.displayName = "Alert";
+
+const AlertTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+AlertTitle.displayName = "AlertTitle";
+
+const AlertDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+AlertDescription.displayName = "AlertDescription";
+
+export { Alert, AlertTitle, AlertDescription };
diff --git a/apps/swc-plugins/components/ui/aspect-ratio.tsx b/apps/swc-plugins/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000..359bc94
--- /dev/null
+++ b/apps/swc-plugins/components/ui/aspect-ratio.tsx
@@ -0,0 +1,7 @@
+"use client";
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
+
+const AspectRatio = AspectRatioPrimitive.Root;
+
+export { AspectRatio };
diff --git a/apps/swc-plugins/components/ui/avatar.tsx b/apps/swc-plugins/components/ui/avatar.tsx
new file mode 100644
index 0000000..8d1b7db
--- /dev/null
+++ b/apps/swc-plugins/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client";
+
+import * as React from "react";
+import * as AvatarPrimitive from "@radix-ui/react-avatar";
+
+import { cn } from "@/lib/utils";
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+Avatar.displayName = AvatarPrimitive.Root.displayName;
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AvatarImage.displayName = AvatarPrimitive.Image.displayName;
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
+
+export { Avatar, AvatarImage, AvatarFallback };
diff --git a/apps/swc-plugins/components/ui/badge.tsx b/apps/swc-plugins/components/ui/badge.tsx
new file mode 100644
index 0000000..476d402
--- /dev/null
+++ b/apps/swc-plugins/components/ui/badge.tsx
@@ -0,0 +1,36 @@
+import * as React from "react";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const badgeVariants = cva(
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ destructive:
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
+ outline: "text-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+);
+
+export interface BadgeProps
+ extends React.HTMLAttributes,
+ VariantProps {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+ return (
+
+ );
+}
+
+export { Badge, badgeVariants };
diff --git a/apps/swc-plugins/components/ui/breadcrumb.tsx b/apps/swc-plugins/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..1725e06
--- /dev/null
+++ b/apps/swc-plugins/components/ui/breadcrumb.tsx
@@ -0,0 +1,115 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { ChevronRight, MoreHorizontal } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+
+const Breadcrumb = React.forwardRef<
+ HTMLElement,
+ React.ComponentPropsWithoutRef<"nav"> & {
+ separator?: React.ReactNode;
+ }
+>(({ ...props }, ref) => );
+Breadcrumb.displayName = "Breadcrumb";
+
+const BreadcrumbList = React.forwardRef<
+ HTMLOListElement,
+ React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+
+));
+BreadcrumbList.displayName = "BreadcrumbList";
+
+const BreadcrumbItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+
+));
+BreadcrumbItem.displayName = "BreadcrumbItem";
+
+const BreadcrumbLink = React.forwardRef<
+ HTMLAnchorElement,
+ React.ComponentPropsWithoutRef<"a"> & {
+ asChild?: boolean;
+ }
+>(({ asChild, className, ...props }, ref) => {
+ const Comp = asChild ? Slot : "a";
+
+ return (
+
+ );
+});
+BreadcrumbLink.displayName = "BreadcrumbLink";
+
+const BreadcrumbPage = React.forwardRef<
+ HTMLSpanElement,
+ React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+
+));
+BreadcrumbPage.displayName = "BreadcrumbPage";
+
+const BreadcrumbSeparator = ({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"li">) => (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+);
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
+
+const BreadcrumbEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+
+
+ More
+
+);
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+};
diff --git a/apps/swc-plugins/components/ui/button.tsx b/apps/swc-plugins/components/ui/button.tsx
new file mode 100644
index 0000000..cd7d5b9
--- /dev/null
+++ b/apps/swc-plugins/components/ui/button.tsx
@@ -0,0 +1,57 @@
+import * as React from "react";
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+);
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean;
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+ return (
+
+ );
+ }
+);
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
diff --git a/apps/swc-plugins/components/ui/calendar.tsx b/apps/swc-plugins/components/ui/calendar.tsx
new file mode 100644
index 0000000..ea63337
--- /dev/null
+++ b/apps/swc-plugins/components/ui/calendar.tsx
@@ -0,0 +1,68 @@
+"use client";
+
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react";
+import { DayPicker } from "react-day-picker";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
+
+export type CalendarProps = React.ComponentProps;
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ ...props
+}: CalendarProps) {
+ return (
+ ,
+ IconRight: ({ ...props }) => (
+
+ ),
+ }}
+ {...props}
+ />
+ );
+}
+Calendar.displayName = "Calendar";
+
+export { Calendar };
diff --git a/apps/swc-plugins/components/ui/card.tsx b/apps/swc-plugins/components/ui/card.tsx
new file mode 100644
index 0000000..fdfcf04
--- /dev/null
+++ b/apps/swc-plugins/components/ui/card.tsx
@@ -0,0 +1,86 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+Card.displayName = "Card";
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardHeader.displayName = "CardHeader";
+
+const CardTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardTitle.displayName = "CardTitle";
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardDescription.displayName = "CardDescription";
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardContent.displayName = "CardContent";
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+));
+CardFooter.displayName = "CardFooter";
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardDescription,
+ CardContent,
+};
diff --git a/apps/swc-plugins/components/ui/carousel.tsx b/apps/swc-plugins/components/ui/carousel.tsx
new file mode 100644
index 0000000..84001f2
--- /dev/null
+++ b/apps/swc-plugins/components/ui/carousel.tsx
@@ -0,0 +1,263 @@
+"use client";
+
+import * as React from "react";
+import useEmblaCarousel, {
+ type UseEmblaCarouselType,
+} from "embla-carousel-react";
+import { ArrowLeft, ArrowRight } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+
+type CarouselApi = UseEmblaCarouselType[1];
+type UseCarouselParameters = Parameters;
+type CarouselOptions = UseCarouselParameters[0];
+type CarouselPlugin = UseCarouselParameters[1];
+
+type CarouselProps = {
+ opts?: CarouselOptions;
+ plugins?: CarouselPlugin;
+ orientation?: "horizontal" | "vertical";
+ setApi?: (api: CarouselApi) => void;
+};
+
+type CarouselContextProps = {
+ carouselRef: ReturnType[0];
+ api: ReturnType[1];
+ scrollPrev: () => void;
+ scrollNext: () => void;
+ canScrollPrev: boolean;
+ canScrollNext: boolean;
+} & CarouselProps;
+
+const CarouselContext = React.createContext(null);
+
+function useCarousel() {
+ const context = React.useContext(CarouselContext);
+
+ if (!context) {
+ throw new Error("useCarousel must be used within a ");
+ }
+
+ return context;
+}
+
+const Carousel = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes & CarouselProps
+>(
+ (
+ {
+ orientation = "horizontal",
+ opts,
+ setApi,
+ plugins,
+ className,
+ children,
+ ...props
+ },
+ ref
+ ) => {
+ const [carouselRef, api] = useEmblaCarousel(
+ {
+ ...opts,
+ axis: orientation === "horizontal" ? "x" : "y",
+ },
+ plugins
+ );
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
+
+ const onSelect = React.useCallback((api: CarouselApi) => {
+ if (!api) {
+ return;
+ }
+
+ setCanScrollPrev(api.canScrollPrev());
+ setCanScrollNext(api.canScrollNext());
+ }, []);
+
+ const scrollPrev = React.useCallback(() => {
+ api?.scrollPrev();
+ }, [api]);
+
+ const scrollNext = React.useCallback(() => {
+ api?.scrollNext();
+ }, [api]);
+
+ const handleKeyDown = React.useCallback(
+ (event: React.KeyboardEvent) => {
+ if (event.key === "ArrowLeft") {
+ event.preventDefault();
+ scrollPrev();
+ } else if (event.key === "ArrowRight") {
+ event.preventDefault();
+ scrollNext();
+ }
+ },
+ [scrollPrev, scrollNext]
+ );
+
+ React.useEffect(() => {
+ if (!api || !setApi) {
+ return;
+ }
+
+ setApi(api);
+ }, [api, setApi]);
+
+ React.useEffect(() => {
+ if (!api) {
+ return;
+ }
+
+ onSelect(api);
+ api.on("reInit", onSelect);
+ api.on("select", onSelect);
+
+ return () => {
+ api?.off("select", onSelect);
+ };
+ }, [api, onSelect]);
+
+ return (
+
+
+ {children}
+
+
+ );
+ }
+);
+Carousel.displayName = "Carousel";
+
+const CarouselContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { carouselRef, orientation } = useCarousel();
+
+ return (
+
+ );
+});
+CarouselContent.displayName = "CarouselContent";
+
+const CarouselItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { orientation } = useCarousel();
+
+ return (
+
+ );
+});
+CarouselItem.displayName = "CarouselItem";
+
+const CarouselPrevious = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
+
+ return (
+
+
+ Previous slide
+
+ );
+});
+CarouselPrevious.displayName = "CarouselPrevious";
+
+const CarouselNext = React.forwardRef<
+ HTMLButtonElement,
+ React.ComponentProps
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
+
+ return (
+
+
+ Next slide
+
+ );
+});
+CarouselNext.displayName = "CarouselNext";
+
+export {
+ type CarouselApi,
+ Carousel,
+ CarouselContent,
+ CarouselItem,
+ CarouselPrevious,
+ CarouselNext,
+};
diff --git a/apps/swc-plugins/components/ui/chart.tsx b/apps/swc-plugins/components/ui/chart.tsx
new file mode 100644
index 0000000..6f36b97
--- /dev/null
+++ b/apps/swc-plugins/components/ui/chart.tsx
@@ -0,0 +1,407 @@
+"use client";
+
+import * as React from "react";
+import * as RechartsPrimitive from "recharts";
+
+import { cn } from "@/lib/utils";
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode;
+ icon?: React.ComponentType;
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ );
+};
+
+type ChartContextProps = {
+ config: ChartConfig;
+};
+
+const ChartContext = React.createContext(null);
+
+function useChart() {
+ const context = React.useContext(ChartContext);
+
+ if (!context) {
+ throw new Error("useChart must be used within a ");
+ }
+
+ return context;
+}
+
+const ChartContainer = React.forwardRef<
+ HTMLDivElement,
+ React.ComponentProps<"div"> & {
+ config: ChartConfig;
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >["children"];
+ }
+>(({ id, className, children, config, ...props }, ref) => {
+ const uniqueId = React.useId();
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+});
+ChartContainer.displayName = "Chart";
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([_, config]) => config.theme || config.color
+ );
+
+ if (!colorConfig.length) {
+ return null;
+ }
+
+ return (
+