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

+
+ + +
+
+); + +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 ( +
+
Loading...
+
+ ); +} 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} + +

+ +
+ + +
+
+ ); +}; 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 }) => ( + SWC Logo +); 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: 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 ( +
+ +
+ ); +}; + +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 ? : null} + + {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) =>