diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100755
index 0312b760..00000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-. "$(dirname -- "$0")/_/husky.sh"
-
-npx lint-staged
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
deleted file mode 100644
index 23fd35f0..00000000
--- a/.vscode/settings.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "editor.formatOnSave": true
-}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..f7f09b6c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,109 @@
+Creative Commons Legal Code
+
+CC0 1.0 Universal
+
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
+ HEREUNDER.
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an "owner") of an original work of authorship
+and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific works
+("Commons") that the public can reliably and without fear of later claims of
+infringement build upon, modify, incorporate in other works, reuse and
+redistribute as freely as possible in any form whatsoever and for any purposes,
+including without limitation commercial purposes. These owners may contribute to
+the Commons to promote the ideal of a free culture and the further production of
+creative, cultural and scientific works, or to gain reputation or greater
+distribution for their Work in part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation of
+additional consideration or compensation, the person associating CC0 with a Work
+(the "Affirmer"), to the extent that he or she is an owner of Copyright and
+Related Rights in the Work, voluntarily elects to apply CC0 to the Work and
+publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+ protected by copyright and related or neighboring rights ("Copyright and
+ Related Rights"). Copyright and Related Rights include, but are not limited
+ to, the following:
+
+i. the right to reproduce, adapt, distribute, perform, display, communicate, and
+translate a Work; ii. moral rights retained by the original author(s) and/or
+performer(s); iii. publicity and privacy rights pertaining to a person's image
+or likeness depicted in a Work; iv. rights protecting against unfair competition
+in regards to a Work, subject to the limitations in paragraph 4(a), below; v.
+rights protecting the extraction, dissemination, use and reuse of data in a
+Work; vi. database rights (such as those arising under Directive 96/9/EC of the
+European Parliament and of the Council of 11 March 1996 on the legal protection
+of databases, and under any national implementation thereof, including any
+amended or successor version of such directive); and vii. other similar,
+equivalent or corresponding rights throughout the world based on applicable law
+or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+ applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+ unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+ and Related Rights and associated claims and causes of action, whether now
+ known or unknown (including existing as well as future claims and causes of
+ action), in the Work (i) in all territories worldwide, (ii) for the maximum
+ duration provided by applicable law or treaty (including future time
+ extensions), (iii) in any current or future medium and for any number of
+ copies, and (iv) for any purpose whatsoever, including without limitation
+ commercial, advertising or promotional purposes (the "Waiver"). Affirmer
+ makes the Waiver for the benefit of each member of the public at large and to
+ the detriment of Affirmer's heirs and successors, fully intending that such
+ Waiver shall not be subject to revocation, rescission, cancellation,
+ termination, or any other legal or equitable action to disrupt the quiet
+ enjoyment of the Work by the public as contemplated by Affirmer's express .
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+ judged legally invalid or ineffective under applicable law, then the Waiver
+ shall be preserved to the maximum extent permitted taking into account
+ Affirmer's express . In addition, to the extent the Waiver is so judged
+ Affirmer hereby grants to each affected person a royalty-free, non
+ transferable, non sublicensable, non exclusive, irrevocable and unconditional
+ license to exercise Affirmer's Copyright and Related Rights in the Work (i)
+ in all territories worldwide, (ii) for the maximum duration provided by
+ applicable law or treaty (including future time extensions), (iii) in any
+ current or future medium and for any number of copies, and (iv) for any
+ purpose whatsoever, including without limitation commercial, advertising or
+ promotional purposes (the "License"). The License shall be deemed effective
+ as of the date CC0 was applied by Affirmer to the Work. Should any part of
+ the License for any reason be judged legally invalid or ineffective under
+ applicable law, such partial invalidity or ineffectiveness shall not
+ invalidate the remainder of the License, and in such case Affirmer hereby
+ affirms that he or she will not (i) exercise any of his or her remaining
+ Copyright and Related Rights in the Work or (ii) assert any associated claims
+ and causes of action with respect to the Work, in either case contrary to
+
+4. Limitations and Disclaimers.
+
+a. No trademark or patent rights held by Affirmer are waived, abandoned,
+surrendered, licensed or otherwise affected by this document. b. Affirmer offers
+the Work as-is and makes no representations or warranties of any kind concerning
+the Work, express, implied, statutory or otherwise, including without limitation
+warranties of title, merchantability, fitness for a particular purpose, non
+infringement, or the absence of latent or other defects, accuracy, or the
+present or absence of errors, whether or not discoverable, all to the greatest
+extent permissible under applicable law. c. Affirmer disclaims responsibility
+for clearing rights of other persons that may apply to the Work or any use
+thereof, including without limitation any person's Copyright and Related Rights
+in the Work. Further, Affirmer disclaims responsibility for obtaining any
+necessary consents, permissions or other rights required for any use of the
+Work. d. Affirmer understands and acknowledges that Creative Commons is not a
+party to this document and has no duty or obligation with respect to this CC0 or
+use of the Work.
diff --git a/README.md b/README.md
deleted file mode 100644
index 49587d9a..00000000
--- a/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# SWC Website
-
-## Built Using
-
-- [Next.js](https://nextjs.org?utm_source=swc)
-- [Nextra](https://nextra.vercel.app)
-- [Vercel](https://vercel.com?utm_source=swc)
-
-[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fswc-project%2Fwebsite)
diff --git a/apps/plugins/.eslintignore b/apps/plugins/.eslintignore
deleted file mode 100644
index 7eaeda27..00000000
--- a/apps/plugins/.eslintignore
+++ /dev/null
@@ -1,3 +0,0 @@
-*.d.ts
-/lib/generated
-/components/ui
diff --git a/apps/plugins/.eslintrc.json b/apps/plugins/.eslintrc.json
deleted file mode 100644
index 4ea74466..00000000
--- a/apps/plugins/.eslintrc.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "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/plugins/.gitignore b/apps/plugins/.gitignore
index 08318f37..289f7928 100644
--- a/apps/plugins/.gitignore
+++ b/apps/plugins/.gitignore
@@ -1,43 +1,43 @@
-# 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/
-
-
-generated/
+# 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/
+
+
+generated/
.turbo/
\ No newline at end of file
diff --git a/apps/plugins/README.md b/apps/plugins/README.md
deleted file mode 100644
index 5ce4a7c6..00000000
--- a/apps/plugins/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-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/plugins/app/(home)/page.tsx b/apps/plugins/app/(home)/page.tsx
index 7deb1d12..1fb6c612 100644
--- a/apps/plugins/app/(home)/page.tsx
+++ b/apps/plugins/app/(home)/page.tsx
@@ -1,33 +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;
+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/plugins/app/api-client-provider.tsx b/apps/plugins/app/api-client-provider.tsx
index 59802691..fbefe211 100644
--- a/apps/plugins/app/api-client-provider.tsx
+++ b/apps/plugins/app/api-client-provider.tsx
@@ -1,38 +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}
-
-
- );
-}
+"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/plugins/app/api/auth/[...nextauth]/route.ts b/apps/plugins/app/api/auth/[...nextauth]/route.ts
index 73228a09..db57ba32 100644
--- a/apps/plugins/app/api/auth/[...nextauth]/route.ts
+++ b/apps/plugins/app/api/auth/[...nextauth]/route.ts
@@ -1,2 +1,3 @@
-import { handlers } from "@/lib/auth";
-export const { GET, POST } = handlers;
+import { handlers } from "@/lib/auth";
+
+export const { GET, POST } = handlers;
diff --git a/apps/plugins/app/api/trpc/[trpc]/route.ts b/apps/plugins/app/api/trpc/[trpc]/route.ts
index 50095b43..7e3ea39b 100644
--- a/apps/plugins/app/api/trpc/[trpc]/route.ts
+++ b/apps/plugins/app/api/trpc/[trpc]/route.ts
@@ -1,6 +1,3 @@
-import { handler } from "@/lib/api/server";
-
-export {
- handler as GET,
- handler as POST
-};
+import { handler } from "@/lib/api/server";
+
+export { handler as GET, handler as POST };
diff --git a/apps/plugins/app/api/update/runtimes/route.ts b/apps/plugins/app/api/update/runtimes/route.ts
index ad3c950a..15bbd848 100644
--- a/apps/plugins/app/api/update/runtimes/route.ts
+++ b/apps/plugins/app/api/update/runtimes/route.ts
@@ -1,13 +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 });
-};
+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/plugins/app/api/update/wasm-plugins/route.ts b/apps/plugins/app/api/update/wasm-plugins/route.ts
index c0e5fa15..aefe4637 100644
--- a/apps/plugins/app/api/update/wasm-plugins/route.ts
+++ b/apps/plugins/app/api/update/wasm-plugins/route.ts
@@ -1,13 +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 });
-};
+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/plugins/app/client-providers.tsx b/apps/plugins/app/client-providers.tsx
index 578a672b..ae464515 100644
--- a/apps/plugins/app/client-providers.tsx
+++ b/apps/plugins/app/client-providers.tsx
@@ -1,13 +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}
-
- );
-}
+"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/plugins/app/import/ranges/page.tsx b/apps/plugins/app/import/ranges/page.tsx
index f4dcc59f..619801b7 100644
--- a/apps/plugins/app/import/ranges/page.tsx
+++ b/apps/plugins/app/import/ranges/page.tsx
@@ -1,45 +1,45 @@
-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: min,
- },
- update: {
- to: max,
- },
- 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";
+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: min,
+ },
+ update: {
+ to: max,
+ },
+ 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/plugins/app/import/runtime/route.ts b/apps/plugins/app/import/runtime/route.ts
index c3763d79..915b58b4 100644
--- a/apps/plugins/app/import/runtime/route.ts
+++ b/apps/plugins/app/import/runtime/route.ts
@@ -1,76 +1,80 @@
-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,
- });
-}
+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/plugins/app/import/swc_core/route.ts b/apps/plugins/app/import/swc_core/route.ts
index 19431064..d9f6bdb6 100644
--- a/apps/plugins/app/import/swc_core/route.ts
+++ b/apps/plugins/app/import/swc_core/route.ts
@@ -1,29 +1,30 @@
-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,
- });
-}
+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/plugins/app/layout.tsx b/apps/plugins/app/layout.tsx
index c1649eac..1c453342 100644
--- a/apps/plugins/app/layout.tsx
+++ b/apps/plugins/app/layout.tsx
@@ -1,31 +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;
+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/plugins/app/loading.tsx b/apps/plugins/app/loading.tsx
index 10e0561a..83b42ce4 100644
--- a/apps/plugins/app/loading.tsx
+++ b/apps/plugins/app/loading.tsx
@@ -1,7 +1,7 @@
-export default function Loading() {
- return (
-
- );
-}
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/apps/plugins/app/robots.ts b/apps/plugins/app/robots.ts
index ace613c7..c4e19496 100644
--- a/apps/plugins/app/robots.ts
+++ b/apps/plugins/app/robots.ts
@@ -1,11 +1,11 @@
-import { MetadataRoute } from "next";
-
-export default function robots(): MetadataRoute.Robots {
- return {
- rules: {
- userAgent: "*",
- allow: "/",
- },
- // sitemap: `${BASE_URL}/sitemap.xml`,
- };
-}
+import { MetadataRoute } from "next";
+
+export default function robots(): MetadataRoute.Robots {
+ return {
+ rules: {
+ userAgent: "*",
+ allow: "/",
+ },
+ // sitemap: `${BASE_URL}/sitemap.xml`,
+ };
+}
diff --git a/apps/plugins/app/versions/from-core/[version]/page.tsx b/apps/plugins/app/versions/from-core/[version]/page.tsx
index 04b64d4d..e2c8c6c4 100644
--- a/apps/plugins/app/versions/from-core/[version]/page.tsx
+++ b/apps/plugins/app/versions/from-core/[version]/page.tsx
@@ -1,25 +1,25 @@
-import { createCaller } from "@/lib/server";
-import { redirect } from "next/navigation";
-
-export default async function Page(
- props: {
- params: Promise<{ version: string }>;
- }
-) {
- const params = await props.params;
-
- const {
- version
- } = params;
-
- const api = await createCaller();
- const compatRange = await api.compatRange.byCoreVersion({
- version,
- });
-
- if (compatRange) {
- return redirect(`/versions/range/${compatRange.id}`);
- }
-
- return No compat range found for swc_core@{version}
;
-}
+import { createCaller } from "@/lib/server";
+import { redirect } from "next/navigation";
+
+export default async function Page(
+ props: {
+ params: Promise<{ version: string }>;
+ }
+) {
+ const params = await props.params;
+
+ const {
+ version
+ } = params;
+
+ const api = await createCaller();
+ const compatRange = await api.compatRange.byCoreVersion({
+ version,
+ });
+
+ if (compatRange) {
+ return redirect(`/versions/range/${compatRange.id}`);
+ }
+
+ return No compat range found for swc_core@{version}
;
+}
diff --git a/apps/plugins/app/versions/from-plugin-runner/[version]/page.tsx b/apps/plugins/app/versions/from-plugin-runner/[version]/page.tsx
index bcfc6ba1..6b097a79 100644
--- a/apps/plugins/app/versions/from-plugin-runner/[version]/page.tsx
+++ b/apps/plugins/app/versions/from-plugin-runner/[version]/page.tsx
@@ -1,25 +1,25 @@
-import { createCaller } from "@/lib/server";
-import { redirect } from "next/navigation";
-
-export default async function Page(
- props: {
- params: Promise<{ version: string }>;
- }
-) {
- const params = await props.params;
-
- const {
- version
- } = params;
-
- const api = await createCaller();
- const compatRange = await api.compatRange.byPluginRunnerVersion({
- version,
- });
-
- if (compatRange) {
- return redirect(`/versions/range/${compatRange.id}`);
- }
-
- return No compat range found for swc_plugin_runner@{version}
;
-}
+import { createCaller } from "@/lib/server";
+import { redirect } from "next/navigation";
+
+export default async function Page(
+ props: {
+ params: Promise<{ version: string }>;
+ }
+) {
+ const params = await props.params;
+
+ const {
+ version
+ } = params;
+
+ const api = await createCaller();
+ const compatRange = await api.compatRange.byPluginRunnerVersion({
+ version,
+ });
+
+ if (compatRange) {
+ return redirect(`/versions/range/${compatRange.id}`);
+ }
+
+ return No compat range found for swc_plugin_runner@{version}
;
+}
diff --git a/apps/plugins/app/versions/layout.tsx b/apps/plugins/app/versions/layout.tsx
index 9b6370c6..5de07db8 100644
--- a/apps/plugins/app/versions/layout.tsx
+++ b/apps/plugins/app/versions/layout.tsx
@@ -1,28 +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;
+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/plugins/app/versions/range/[compatRangeId]/components/compat-range-header.tsx b/apps/plugins/app/versions/range/[compatRangeId]/components/compat-range-header.tsx
index 873872c0..ae390f62 100644
--- a/apps/plugins/app/versions/range/[compatRangeId]/components/compat-range-header.tsx
+++ b/apps/plugins/app/versions/range/[compatRangeId]/components/compat-range-header.tsx
@@ -1,47 +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
-
-
- );
-};
+"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/plugins/app/versions/range/[compatRangeId]/components/compat-range-tables.tsx b/apps/plugins/app/versions/range/[compatRangeId]/components/compat-range-tables.tsx
index a4d2d6bc..c85af70c 100644
--- a/apps/plugins/app/versions/range/[compatRangeId]/components/compat-range-tables.tsx
+++ b/apps/plugins/app/versions/range/[compatRangeId]/components/compat-range-tables.tsx
@@ -1,97 +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}
-
-
- ))}
-
-
-
- >
- );
-};
+"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/plugins/app/versions/range/[compatRangeId]/page.tsx b/apps/plugins/app/versions/range/[compatRangeId]/page.tsx
index c519f10c..50d417a2 100644
--- a/apps/plugins/app/versions/range/[compatRangeId]/page.tsx
+++ b/apps/plugins/app/versions/range/[compatRangeId]/page.tsx
@@ -1,29 +1,29 @@
-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: Promise<{
- compatRangeId: string;
- }>;
-};
-
-const CompatRangePage: FC = async ({ params }) => {
- const { compatRangeId } = await params;
-
- return (
-
-
-
-
- );
-};
-
-export default CompatRangePage;
+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: Promise<{
+ compatRangeId: string;
+ }>;
+};
+
+const CompatRangePage: FC = async ({ params }) => {
+ const { compatRangeId } = await params;
+
+ return (
+
+
+
+
+ );
+};
+
+export default CompatRangePage;
diff --git a/apps/plugins/app/versions/range/components/range-table.tsx b/apps/plugins/app/versions/range/components/range-table.tsx
index 3490a483..bdf3255d 100644
--- a/apps/plugins/app/versions/range/components/range-table.tsx
+++ b/apps/plugins/app/versions/range/components/range-table.tsx
@@ -1,61 +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}
-
-
- ))}
-
-
-
- );
-};
+"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/plugins/app/versions/range/page.tsx b/apps/plugins/app/versions/range/page.tsx
index af7627f0..933ddcfc 100644
--- a/apps/plugins/app/versions/range/page.tsx
+++ b/apps/plugins/app/versions/range/page.tsx
@@ -1,19 +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;
+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/plugins/components.json b/apps/plugins/components.json
index 835bc876..5da5653a 100644
--- a/apps/plugins/components.json
+++ b/apps/plugins/components.json
@@ -1,17 +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"
- }
-}
+{
+ "$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/plugins/components/dynamic.tsx b/apps/plugins/components/dynamic.tsx
index a3c18323..68dfec44 100644
--- a/apps/plugins/components/dynamic.tsx
+++ b/apps/plugins/components/dynamic.tsx
@@ -1,17 +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} >;
-};
+"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/plugins/components/logo/index.tsx b/apps/plugins/components/logo/index.tsx
index 102fea65..de66074c 100644
--- a/apps/plugins/components/logo/index.tsx
+++ b/apps/plugins/components/logo/index.tsx
@@ -1,15 +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 }) => (
-
-);
+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/plugins/components/logo/swc.svg b/apps/plugins/components/logo/swc.svg
index b0ca9e35..01aef532 100644
--- a/apps/plugins/components/logo/swc.svg
+++ b/apps/plugins/components/logo/swc.svg
@@ -1,12 +1,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/plugins/components/runtime-version-selector.tsx b/apps/plugins/components/runtime-version-selector.tsx
index 272fffd2..b5d8b35e 100644
--- a/apps/plugins/components/runtime-version-selector.tsx
+++ b/apps/plugins/components/runtime-version-selector.tsx
@@ -1,52 +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"
- />
-
- );
-};
+"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/plugins/components/select.tsx b/apps/plugins/components/select.tsx
index 2090da4a..3ca02a65 100644
--- a/apps/plugins/components/select.tsx
+++ b/apps/plugins/components/select.tsx
@@ -1,196 +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}
-
-
-
- );
- })}
-
-
-
-
-
- );
-};
+"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/plugins/components/ui/accordion.tsx b/apps/plugins/components/ui/accordion.tsx
index b1e2db38..83d74916 100644
--- a/apps/plugins/components/ui/accordion.tsx
+++ b/apps/plugins/components/ui/accordion.tsx
@@ -1,58 +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 };
+"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/plugins/components/ui/alert-dialog.tsx b/apps/plugins/components/ui/alert-dialog.tsx
index e9799b53..d4abe1a9 100644
--- a/apps/plugins/components/ui/alert-dialog.tsx
+++ b/apps/plugins/components/ui/alert-dialog.tsx
@@ -1,141 +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,
-};
+"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/plugins/components/ui/alert.tsx b/apps/plugins/components/ui/alert.tsx
index 2d6922cd..34160af2 100644
--- a/apps/plugins/components/ui/alert.tsx
+++ b/apps/plugins/components/ui/alert.tsx
@@ -1,62 +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 };
+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/plugins/components/ui/aspect-ratio.tsx b/apps/plugins/components/ui/aspect-ratio.tsx
index 359bc940..e7de2dd7 100644
--- a/apps/plugins/components/ui/aspect-ratio.tsx
+++ b/apps/plugins/components/ui/aspect-ratio.tsx
@@ -1,7 +1,7 @@
-"use client";
-
-import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
-
-const AspectRatio = AspectRatioPrimitive.Root;
-
-export { AspectRatio };
+"use client";
+
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
+
+const AspectRatio = AspectRatioPrimitive.Root;
+
+export { AspectRatio };
diff --git a/apps/plugins/components/ui/avatar.tsx b/apps/plugins/components/ui/avatar.tsx
index 8d1b7db7..d0ed29e4 100644
--- a/apps/plugins/components/ui/avatar.tsx
+++ b/apps/plugins/components/ui/avatar.tsx
@@ -1,50 +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 };
+"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/plugins/components/ui/breadcrumb.tsx b/apps/plugins/components/ui/breadcrumb.tsx
index 1725e067..4b60c0fc 100644
--- a/apps/plugins/components/ui/breadcrumb.tsx
+++ b/apps/plugins/components/ui/breadcrumb.tsx
@@ -1,115 +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,
-};
+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/plugins/components/ui/calendar.tsx b/apps/plugins/components/ui/calendar.tsx
index ea633373..5fa99c01 100644
--- a/apps/plugins/components/ui/calendar.tsx
+++ b/apps/plugins/components/ui/calendar.tsx
@@ -1,68 +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 };
+"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/plugins/components/ui/carousel.tsx b/apps/plugins/components/ui/carousel.tsx
index 84001f25..040109bf 100644
--- a/apps/plugins/components/ui/carousel.tsx
+++ b/apps/plugins/components/ui/carousel.tsx
@@ -1,263 +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,
-};
+"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/plugins/components/ui/collapsible.tsx b/apps/plugins/components/ui/collapsible.tsx
index cb003d17..b87728f0 100644
--- a/apps/plugins/components/ui/collapsible.tsx
+++ b/apps/plugins/components/ui/collapsible.tsx
@@ -1,11 +1,11 @@
-"use client";
-
-import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
-
-const Collapsible = CollapsiblePrimitive.Root;
-
-const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
-
-const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
-
-export { Collapsible, CollapsibleTrigger, CollapsibleContent };
+"use client";
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
+
+const Collapsible = CollapsiblePrimitive.Root;
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/apps/plugins/components/ui/drawer.tsx b/apps/plugins/components/ui/drawer.tsx
index 92cf2ff7..6e123c36 100644
--- a/apps/plugins/components/ui/drawer.tsx
+++ b/apps/plugins/components/ui/drawer.tsx
@@ -1,118 +1,118 @@
-"use client";
-
-import * as React from "react";
-import { Drawer as DrawerPrimitive } from "vaul";
-
-import { cn } from "@/lib/utils";
-
-const Drawer = ({
- shouldScaleBackground = true,
- ...props
-}: React.ComponentProps) => (
-
-);
-Drawer.displayName = "Drawer";
-
-const DrawerTrigger = DrawerPrimitive.Trigger;
-
-const DrawerPortal = DrawerPrimitive.Portal;
-
-const DrawerClose = DrawerPrimitive.Close;
-
-const DrawerOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
-
-const DrawerContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
-
- {children}
-
-
-));
-DrawerContent.displayName = "DrawerContent";
-
-const DrawerHeader = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-);
-DrawerHeader.displayName = "DrawerHeader";
-
-const DrawerFooter = ({
- className,
- ...props
-}: React.HTMLAttributes) => (
-
-);
-DrawerFooter.displayName = "DrawerFooter";
-
-const DrawerTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
-
-const DrawerDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
-
-export {
- Drawer,
- DrawerPortal,
- DrawerOverlay,
- DrawerTrigger,
- DrawerClose,
- DrawerContent,
- DrawerHeader,
- DrawerFooter,
- DrawerTitle,
- DrawerDescription,
-};
+"use client";
+
+import * as React from "react";
+import { Drawer as DrawerPrimitive } from "vaul";
+
+import { cn } from "@/lib/utils";
+
+const Drawer = ({
+ shouldScaleBackground = true,
+ ...props
+}: React.ComponentProps) => (
+
+);
+Drawer.displayName = "Drawer";
+
+const DrawerTrigger = DrawerPrimitive.Trigger;
+
+const DrawerPortal = DrawerPrimitive.Portal;
+
+const DrawerClose = DrawerPrimitive.Close;
+
+const DrawerOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
+
+const DrawerContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+));
+DrawerContent.displayName = "DrawerContent";
+
+const DrawerHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+DrawerHeader.displayName = "DrawerHeader";
+
+const DrawerFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+DrawerFooter.displayName = "DrawerFooter";
+
+const DrawerTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
+
+const DrawerDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
+
+export {
+ Drawer,
+ DrawerPortal,
+ DrawerOverlay,
+ DrawerTrigger,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerFooter,
+ DrawerTitle,
+ DrawerDescription,
+};
diff --git a/apps/plugins/components/ui/form.tsx b/apps/plugins/components/ui/form.tsx
index cd242719..b19d2b97 100644
--- a/apps/plugins/components/ui/form.tsx
+++ b/apps/plugins/components/ui/form.tsx
@@ -1,179 +1,179 @@
-"use client";
-
-import * as React from "react";
-import * as LabelPrimitive from "@radix-ui/react-label";
-import { Slot } from "@radix-ui/react-slot";
-import {
- Controller,
- ControllerProps,
- FieldPath,
- FieldValues,
- FormProvider,
- useFormContext,
-} from "react-hook-form";
-
-import { cn } from "@/lib/utils";
-import { Label } from "@/components/ui/label";
-
-const Form = FormProvider;
-
-type FormFieldContextValue<
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
-> = {
- name: TName;
-};
-
-const FormFieldContext = React.createContext(
- {} as FormFieldContextValue
-);
-
-const FormField = <
- TFieldValues extends FieldValues = FieldValues,
- TName extends FieldPath = FieldPath
->({
- ...props
-}: ControllerProps) => {
- return (
-
-
-
- );
-};
-
-const useFormField = () => {
- const fieldContext = React.useContext(FormFieldContext);
- const itemContext = React.useContext(FormItemContext);
- const { getFieldState, formState } = useFormContext();
-
- const fieldState = getFieldState(fieldContext.name, formState);
-
- if (!fieldContext) {
- throw new Error("useFormField should be used within ");
- }
-
- const { id } = itemContext;
-
- return {
- id,
- name: fieldContext.name,
- formItemId: `${id}-form-item`,
- formDescriptionId: `${id}-form-item-description`,
- formMessageId: `${id}-form-item-message`,
- ...fieldState,
- };
-};
-
-type FormItemContextValue = {
- id: string;
-};
-
-const FormItemContext = React.createContext(
- {} as FormItemContextValue
-);
-
-const FormItem = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => {
- const id = React.useId();
-
- return (
-
-
-
- );
-});
-FormItem.displayName = "FormItem";
-
-const FormLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => {
- const { error, formItemId } = useFormField();
-
- return (
-
- );
-});
-FormLabel.displayName = "FormLabel";
-
-const FormControl = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ ...props }, ref) => {
- const { error, formItemId, formDescriptionId, formMessageId } =
- useFormField();
-
- return (
-
- );
-});
-FormControl.displayName = "FormControl";
-
-const FormDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => {
- const { formDescriptionId } = useFormField();
-
- return (
-
- );
-});
-FormDescription.displayName = "FormDescription";
-
-const FormMessage = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, children, ...props }, ref) => {
- const { error, formMessageId } = useFormField();
- const body = error ? String(error?.message) : children;
-
- if (!body) {
- return null;
- }
-
- return (
-
- {body}
-
- );
-});
-FormMessage.displayName = "FormMessage";
-
-export {
- useFormField,
- Form,
- FormItem,
- FormLabel,
- FormControl,
- FormDescription,
- FormMessage,
- FormField,
-};
+"use client";
+
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { Slot } from "@radix-ui/react-slot";
+import {
+ Controller,
+ ControllerProps,
+ FieldPath,
+ FieldValues,
+ FormProvider,
+ useFormContext,
+} from "react-hook-form";
+
+import { cn } from "@/lib/utils";
+import { Label } from "@/components/ui/label";
+
+const Form = FormProvider;
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+> = {
+ name: TName;
+};
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue
+);
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ );
+};
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext);
+ const itemContext = React.useContext(FormItemContext);
+ const { getFieldState, formState } = useFormContext();
+
+ const fieldState = getFieldState(fieldContext.name, formState);
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ");
+ }
+
+ const { id } = itemContext;
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ };
+};
+
+type FormItemContextValue = {
+ id: string;
+};
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue
+);
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const id = React.useId();
+
+ return (
+
+
+
+ );
+});
+FormItem.displayName = "FormItem";
+
+const FormLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField();
+
+ return (
+
+ );
+});
+FormLabel.displayName = "FormLabel";
+
+const FormControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } =
+ useFormField();
+
+ return (
+
+ );
+});
+FormControl.displayName = "FormControl";
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField();
+
+ return (
+
+ );
+});
+FormDescription.displayName = "FormDescription";
+
+const FormMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField();
+ const body = error ? String(error?.message) : children;
+
+ if (!body) {
+ return null;
+ }
+
+ return (
+
+ {body}
+
+ );
+});
+FormMessage.displayName = "FormMessage";
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+};
diff --git a/apps/plugins/components/ui/label.tsx b/apps/plugins/components/ui/label.tsx
index 6ce7f61e..c69f20c8 100644
--- a/apps/plugins/components/ui/label.tsx
+++ b/apps/plugins/components/ui/label.tsx
@@ -1,26 +1,26 @@
-"use client";
-
-import * as React from "react";
-import * as LabelPrimitive from "@radix-ui/react-label";
-import { cva, type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-
-const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
-);
-
-const Label = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef &
- VariantProps
->(({ className, ...props }, ref) => (
-
-));
-Label.displayName = LabelPrimitive.Root.displayName;
-
-export { Label };
+"use client";
+
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+);
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+));
+Label.displayName = LabelPrimitive.Root.displayName;
+
+export { Label };
diff --git a/apps/plugins/components/ui/pagination.tsx b/apps/plugins/components/ui/pagination.tsx
index 27034588..8cacc65c 100644
--- a/apps/plugins/components/ui/pagination.tsx
+++ b/apps/plugins/components/ui/pagination.tsx
@@ -1,117 +1,117 @@
-import * as React from "react";
-import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
-
-import { cn } from "@/lib/utils";
-import { ButtonProps, buttonVariants } from "@/components/ui/button";
-
-const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
-
-);
-Pagination.displayName = "Pagination";
-
-const PaginationContent = React.forwardRef<
- HTMLUListElement,
- React.ComponentProps<"ul">
->(({ className, ...props }, ref) => (
-
-));
-PaginationContent.displayName = "PaginationContent";
-
-const PaginationItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentProps<"li">
->(({ className, ...props }, ref) => (
-
-));
-PaginationItem.displayName = "PaginationItem";
-
-type PaginationLinkProps = {
- isActive?: boolean;
-} & Pick &
- React.ComponentProps<"a">;
-
-const PaginationLink = ({
- className,
- isActive,
- size = "icon",
- ...props
-}: PaginationLinkProps) => (
-
-);
-PaginationLink.displayName = "PaginationLink";
-
-const PaginationPrevious = ({
- className,
- ...props
-}: React.ComponentProps) => (
-
-
- Previous
-
-);
-PaginationPrevious.displayName = "PaginationPrevious";
-
-const PaginationNext = ({
- className,
- ...props
-}: React.ComponentProps) => (
-
- Next
-
-
-);
-PaginationNext.displayName = "PaginationNext";
-
-const PaginationEllipsis = ({
- className,
- ...props
-}: React.ComponentProps<"span">) => (
-
-
- More pages
-
-);
-PaginationEllipsis.displayName = "PaginationEllipsis";
-
-export {
- Pagination,
- PaginationContent,
- PaginationEllipsis,
- PaginationItem,
- PaginationLink,
- PaginationNext,
- PaginationPrevious,
-};
+import * as React from "react";
+import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { ButtonProps, buttonVariants } from "@/components/ui/button";
+
+const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
+
+);
+Pagination.displayName = "Pagination";
+
+const PaginationContent = React.forwardRef<
+ HTMLUListElement,
+ React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+
+));
+PaginationContent.displayName = "PaginationContent";
+
+const PaginationItem = React.forwardRef<
+ HTMLLIElement,
+ React.ComponentProps<"li">
+>(({ className, ...props }, ref) => (
+
+));
+PaginationItem.displayName = "PaginationItem";
+
+type PaginationLinkProps = {
+ isActive?: boolean;
+} & Pick &
+ React.ComponentProps<"a">;
+
+const PaginationLink = ({
+ className,
+ isActive,
+ size = "icon",
+ ...props
+}: PaginationLinkProps) => (
+
+);
+PaginationLink.displayName = "PaginationLink";
+
+const PaginationPrevious = ({
+ className,
+ ...props
+}: React.ComponentProps) => (
+
+
+ Previous
+
+);
+PaginationPrevious.displayName = "PaginationPrevious";
+
+const PaginationNext = ({
+ className,
+ ...props
+}: React.ComponentProps) => (
+
+ Next
+
+
+);
+PaginationNext.displayName = "PaginationNext";
+
+const PaginationEllipsis = ({
+ className,
+ ...props
+}: React.ComponentProps<"span">) => (
+
+
+ More pages
+
+);
+PaginationEllipsis.displayName = "PaginationEllipsis";
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationEllipsis,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+};
diff --git a/apps/plugins/components/ui/progress.tsx b/apps/plugins/components/ui/progress.tsx
index 48995d1b..2b48a334 100644
--- a/apps/plugins/components/ui/progress.tsx
+++ b/apps/plugins/components/ui/progress.tsx
@@ -1,28 +1,28 @@
-"use client";
-
-import * as React from "react";
-import * as ProgressPrimitive from "@radix-ui/react-progress";
-
-import { cn } from "@/lib/utils";
-
-const Progress = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, value, ...props }, ref) => (
-
-
-
-));
-Progress.displayName = ProgressPrimitive.Root.displayName;
-
-export { Progress };
+"use client";
+
+import * as React from "react";
+import * as ProgressPrimitive from "@radix-ui/react-progress";
+
+import { cn } from "@/lib/utils";
+
+const Progress = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, value, ...props }, ref) => (
+
+
+
+));
+Progress.displayName = ProgressPrimitive.Root.displayName;
+
+export { Progress };
diff --git a/apps/plugins/components/ui/scroll-area.tsx b/apps/plugins/components/ui/scroll-area.tsx
index 8c999674..b567d233 100644
--- a/apps/plugins/components/ui/scroll-area.tsx
+++ b/apps/plugins/components/ui/scroll-area.tsx
@@ -1,50 +1,50 @@
-"use client";
-
-import * as React from "react";
-import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
-
-import { cn } from "@/lib/utils";
-
-const ScrollArea = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
- {children}
-
-
-
-
-));
-ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
-
-const ScrollBar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef<
- typeof ScrollAreaPrimitive.ScrollAreaScrollbar
- >
->(({ className, orientation = "vertical", ...props }, ref) => (
-
-
-
-));
-ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
-
-export { ScrollArea, ScrollBar };
+"use client";
+
+import * as React from "react";
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
+
+import { cn } from "@/lib/utils";
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+));
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef<
+ typeof ScrollAreaPrimitive.ScrollAreaScrollbar
+ >
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+));
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
+
+export { ScrollArea, ScrollBar };
diff --git a/apps/plugins/components/ui/separator.tsx b/apps/plugins/components/ui/separator.tsx
index 2af4ec89..e412336b 100644
--- a/apps/plugins/components/ui/separator.tsx
+++ b/apps/plugins/components/ui/separator.tsx
@@ -1,33 +1,33 @@
-"use client";
-
-import * as React from "react";
-import * as SeparatorPrimitive from "@radix-ui/react-separator";
-
-import { cn } from "@/lib/utils";
-
-const Separator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(
- (
- { className, orientation = "horizontal", decorative = true, ...props },
- ref
- ) => (
-
- )
-);
-Separator.displayName = SeparatorPrimitive.Root.displayName;
-
-export { Separator };
+"use client";
+
+import * as React from "react";
+import * as SeparatorPrimitive from "@radix-ui/react-separator";
+
+import { cn } from "@/lib/utils";
+
+const Separator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(
+ (
+ { className, orientation = "horizontal", decorative = true, ...props },
+ ref
+ ) => (
+
+ )
+);
+Separator.displayName = SeparatorPrimitive.Root.displayName;
+
+export { Separator };
diff --git a/apps/plugins/components/ui/skeleton.tsx b/apps/plugins/components/ui/skeleton.tsx
index c5c1274c..d0b0cb6c 100644
--- a/apps/plugins/components/ui/skeleton.tsx
+++ b/apps/plugins/components/ui/skeleton.tsx
@@ -1,15 +1,15 @@
-import { cn } from "@/lib/utils";
-
-function Skeleton({
- className,
- ...props
-}: React.HTMLAttributes) {
- return (
-
- );
-}
-
-export { Skeleton };
+import { cn } from "@/lib/utils";
+
+function Skeleton({
+ className,
+ ...props
+}: React.HTMLAttributes) {
+ return (
+
+ );
+}
+
+export { Skeleton };
diff --git a/apps/plugins/components/ui/sonner.tsx b/apps/plugins/components/ui/sonner.tsx
index d8dc1cc2..19bfa8c1 100644
--- a/apps/plugins/components/ui/sonner.tsx
+++ b/apps/plugins/components/ui/sonner.tsx
@@ -1,30 +1,30 @@
-"use client";
-
-import { useTheme } from "next-themes";
-import { Toaster as Sonner } from "sonner";
-
-type ToasterProps = React.ComponentProps;
-
-const Toaster = ({ ...props }: ToasterProps) => {
- const { theme = "system" } = useTheme();
-
- return (
-
- );
-};
-
-export { Toaster };
+"use client";
+
+import { useTheme } from "next-themes";
+import { Toaster as Sonner } from "sonner";
+
+type ToasterProps = React.ComponentProps;
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = "system" } = useTheme();
+
+ return (
+
+ );
+};
+
+export { Toaster };
diff --git a/apps/plugins/components/ui/toaster.tsx b/apps/plugins/components/ui/toaster.tsx
index 3391595e..a2863a27 100644
--- a/apps/plugins/components/ui/toaster.tsx
+++ b/apps/plugins/components/ui/toaster.tsx
@@ -1,43 +1,43 @@
-"use client";
-
-import {
- Toast,
- ToastClose,
- ToastDescription,
- ToastProvider,
- ToastTitle,
- ToastViewport,
-} from "@/components/ui/toast";
-import { useToast } from "@/components/ui/use-toast";
-
-export function Toaster() {
- const { toasts } = useToast();
-
- return (
-
- {toasts.map(function ({
- id,
- title,
- description,
- action,
- ...props
- }) {
- return (
-
-
- {title && {title} }
- {description && (
-
- {description}
-
- )}
-
- {action}
-
-
- );
- })}
-
-
- );
-}
+"use client";
+
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/toast";
+import { useToast } from "@/components/ui/use-toast";
+
+export function Toaster() {
+ const { toasts } = useToast();
+
+ return (
+
+ {toasts.map(function ({
+ id,
+ title,
+ description,
+ action,
+ ...props
+ }) {
+ return (
+
+
+ {title && {title} }
+ {description && (
+
+ {description}
+
+ )}
+
+ {action}
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/apps/plugins/components/ui/toggle-group.tsx b/apps/plugins/components/ui/toggle-group.tsx
index 5cb44985..c6d7145d 100644
--- a/apps/plugins/components/ui/toggle-group.tsx
+++ b/apps/plugins/components/ui/toggle-group.tsx
@@ -1,61 +1,61 @@
-"use client";
-
-import * as React from "react";
-import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
-import { type VariantProps } from "class-variance-authority";
-
-import { cn } from "@/lib/utils";
-import { toggleVariants } from "@/components/ui/toggle";
-
-const ToggleGroupContext = React.createContext<
- VariantProps
->({
- size: "default",
- variant: "default",
-});
-
-const ToggleGroup = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef &
- VariantProps
->(({ className, variant, size, children, ...props }, ref) => (
-
-
- {children}
-
-
-));
-
-ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
-
-const ToggleGroupItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef &
- VariantProps
->(({ className, children, variant, size, ...props }, ref) => {
- const context = React.useContext(ToggleGroupContext);
-
- return (
-
- {children}
-
- );
-});
-
-ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
-
-export { ToggleGroup, ToggleGroupItem };
+"use client";
+
+import * as React from "react";
+import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group";
+import { type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+import { toggleVariants } from "@/components/ui/toggle";
+
+const ToggleGroupContext = React.createContext<
+ VariantProps
+>({
+ size: "default",
+ variant: "default",
+});
+
+const ToggleGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, size, children, ...props }, ref) => (
+
+
+ {children}
+
+
+));
+
+ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
+
+const ToggleGroupItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, children, variant, size, ...props }, ref) => {
+ const context = React.useContext(ToggleGroupContext);
+
+ return (
+
+ {children}
+
+ );
+});
+
+ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
+
+export { ToggleGroup, ToggleGroupItem };
diff --git a/apps/plugins/components/ui/tooltip.tsx b/apps/plugins/components/ui/tooltip.tsx
index 0990b902..e8ea5794 100644
--- a/apps/plugins/components/ui/tooltip.tsx
+++ b/apps/plugins/components/ui/tooltip.tsx
@@ -1,30 +1,30 @@
-"use client";
-
-import * as React from "react";
-import * as TooltipPrimitive from "@radix-ui/react-tooltip";
-
-import { cn } from "@/lib/utils";
-
-const TooltipProvider = TooltipPrimitive.Provider;
-
-const Tooltip = TooltipPrimitive.Root;
-
-const TooltipTrigger = TooltipPrimitive.Trigger;
-
-const TooltipContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 4, ...props }, ref) => (
-
-));
-TooltipContent.displayName = TooltipPrimitive.Content.displayName;
-
-export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
+"use client";
+
+import * as React from "react";
+import * as TooltipPrimitive from "@radix-ui/react-tooltip";
+
+import { cn } from "@/lib/utils";
+
+const TooltipProvider = TooltipPrimitive.Provider;
+
+const Tooltip = TooltipPrimitive.Root;
+
+const TooltipTrigger = TooltipPrimitive.Trigger;
+
+const TooltipContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+));
+TooltipContent.displayName = TooltipPrimitive.Content.displayName;
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/apps/plugins/components/ui/use-toast.ts b/apps/plugins/components/ui/use-toast.ts
index 42def7cc..816bcf45 100644
--- a/apps/plugins/components/ui/use-toast.ts
+++ b/apps/plugins/components/ui/use-toast.ts
@@ -1,192 +1,207 @@
-"use client";
-
-// Inspired by react-hot-toast library
-import * as React from "react";
-
-import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
-
-const TOAST_LIMIT = 1;
-const TOAST_REMOVE_DELAY = 1000000;
-
-type ToasterToast = ToastProps & {
- id: string;
- title?: React.ReactNode;
- description?: React.ReactNode;
- action?: ToastActionElement;
-};
-
-const actionTypes = {
- ADD_TOAST: "ADD_TOAST",
- UPDATE_TOAST: "UPDATE_TOAST",
- DISMISS_TOAST: "DISMISS_TOAST",
- REMOVE_TOAST: "REMOVE_TOAST",
-} as const;
-
-let count = 0;
-
-function genId() {
- count = (count + 1) % Number.MAX_SAFE_INTEGER;
- return count.toString();
-}
-
-type ActionType = typeof actionTypes;
-
-type Action =
- | {
- type: ActionType["ADD_TOAST"];
- toast: ToasterToast;
- }
- | {
- type: ActionType["UPDATE_TOAST"];
- toast: Partial;
- }
- | {
- type: ActionType["DISMISS_TOAST"];
- toastId?: ToasterToast["id"];
- }
- | {
- type: ActionType["REMOVE_TOAST"];
- toastId?: ToasterToast["id"];
- };
-
-interface State {
- toasts: ToasterToast[];
-}
-
-const toastTimeouts = new Map>();
-
-const addToRemoveQueue = (toastId: string) => {
- if (toastTimeouts.has(toastId)) {
- return;
- }
-
- const timeout = setTimeout(() => {
- toastTimeouts.delete(toastId);
- dispatch({
- type: "REMOVE_TOAST",
- toastId: toastId,
- });
- }, TOAST_REMOVE_DELAY);
-
- toastTimeouts.set(toastId, timeout);
-};
-
-export const reducer = (state: State, action: Action): State => {
- switch (action.type) {
- case "ADD_TOAST":
- return {
- ...state,
- toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
- };
-
- case "UPDATE_TOAST":
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === action.toast.id ? { ...t, ...action.toast } : t
- ),
- };
-
- case "DISMISS_TOAST": {
- const { toastId } = action;
-
- // ! Side effects ! - This could be extracted into a dismissToast() action,
- // but I'll keep it here for simplicity
- if (toastId) {
- addToRemoveQueue(toastId);
- } else {
- state.toasts.forEach((toast) => {
- addToRemoveQueue(toast.id);
- });
- }
-
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === toastId || toastId === undefined
- ? {
- ...t,
- open: false,
- }
- : t
- ),
- };
- }
- case "REMOVE_TOAST":
- if (action.toastId === undefined) {
- return {
- ...state,
- toasts: [],
- };
- }
- return {
- ...state,
- toasts: state.toasts.filter((t) => t.id !== action.toastId),
- };
- }
-};
-
-const listeners: Array<(state: State) => void> = [];
-
-let memoryState: State = { toasts: [] };
-
-function dispatch(action: Action) {
- memoryState = reducer(memoryState, action);
- listeners.forEach((listener) => {
- listener(memoryState);
- });
-}
-
-type Toast = Omit;
-
-function toast({ ...props }: Toast) {
- const id = genId();
-
- const update = (props: ToasterToast) =>
- dispatch({
- type: "UPDATE_TOAST",
- toast: { ...props, id },
- });
- const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
-
- dispatch({
- type: "ADD_TOAST",
- toast: {
- ...props,
- id,
- open: true,
- onOpenChange: (open) => {
- if (!open) dismiss();
- },
- },
- });
-
- return {
- id: id,
- dismiss,
- update,
- };
-}
-
-function useToast() {
- const [state, setState] = React.useState(memoryState);
-
- React.useEffect(() => {
- listeners.push(setState);
- return () => {
- const index = listeners.indexOf(setState);
- if (index > -1) {
- listeners.splice(index, 1);
- }
- };
- }, [state]);
-
- return {
- ...state,
- toast,
- dismiss: (toastId?: string) =>
- dispatch({ type: "DISMISS_TOAST", toastId }),
- };
-}
-
-export { useToast, toast };
+"use client";
+
+// Inspired by react-hot-toast library
+import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
+import * as React from "react";
+
+const TOAST_LIMIT = 1;
+
+const TOAST_REMOVE_DELAY = 1000000;
+
+type ToasterToast = ToastProps & {
+ id: string;
+
+ title?: React.ReactNode;
+
+ description?: React.ReactNode;
+
+ action?: ToastActionElement;
+};
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
+} as const;
+
+let count = 0;
+
+function genId() {
+ count = (count + 1) % Number.MAX_SAFE_INTEGER;
+
+ return count.toString();
+}
+
+type ActionType = typeof actionTypes;
+
+type Action =
+ | {
+ type: ActionType["ADD_TOAST"];
+
+ toast: ToasterToast;
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"];
+
+ toast: Partial;
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"];
+
+ toastId?: ToasterToast["id"];
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"];
+
+ toastId?: ToasterToast["id"];
+ };
+
+interface State {
+ toasts: ToasterToast[];
+}
+
+const toastTimeouts = new Map>();
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return;
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId);
+
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ });
+ }, TOAST_REMOVE_DELAY);
+
+ toastTimeouts.set(toastId, timeout);
+};
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ };
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t,
+ ),
+ };
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action;
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId);
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id);
+ });
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t,
+ ),
+ };
+ }
+
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ };
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ };
+ }
+};
+
+const listeners: Array<(state: State) => void> = [];
+
+let memoryState: State = { toasts: [] };
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action);
+
+ listeners.forEach((listener) => {
+ listener(memoryState);
+ });
+}
+
+type Toast = Omit;
+
+function toast({ ...props }: Toast) {
+ const id = genId();
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ });
+
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss();
+ },
+ },
+ });
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ };
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState);
+
+ React.useEffect(() => {
+ listeners.push(setState);
+
+ return () => {
+ const index = listeners.indexOf(setState);
+
+ if (index > -1) {
+ listeners.splice(index, 1);
+ }
+ };
+ }, [state]);
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) =>
+ dispatch({ type: "DISMISS_TOAST", toastId }),
+ };
+}
+
+export { useToast, toast };
diff --git a/apps/plugins/data/ranges.json b/apps/plugins/data/ranges.json
index c0aa1f1a..bfde6d07 100644
--- a/apps/plugins/data/ranges.json
+++ b/apps/plugins/data/ranges.json
@@ -1,94 +1,94 @@
-[
- {
- "min": "0.54.0",
- "max": "0.59.40"
- },
- {
- "min": "0.61.0",
- "max": "0.64.10"
- },
- {
- "min": "0.66.0",
- "max": "0.68.0"
- },
- {
- "min": "0.69.0",
- "max": "0.72.3"
- },
- {
- "min": "0.72.4",
- "max": "0.74.6"
- },
- {
- "min": "0.75.0",
- "max": "0.75.48"
- },
- {
- "min": "0.76.0",
- "max": "0.77.0"
- },
- {
- "min": "0.78.0",
- "max": "0.78.28"
- },
- {
- "min": "0.79.0",
- "max": "0.81.8"
- },
- {
- "min": "0.82.0",
- "max": "0.87.28"
- },
- {
- "min": "0.88.0",
- "max": "0.89.8"
- },
- {
- "min": "0.90.0",
- "max": "0.90.37"
- },
- {
- "min": "0.91.0",
- "max": "0.93.4"
- },
- {
- "min": "0.94.0",
- "max": "0.94.0"
- },
- {
- "min": "0.95.0",
- "max": "0.96.9"
- },
- {
- "min": "0.98.0",
- "max": "0.98.0"
- },
- {
- "min": "0.99.0",
- "max": "0.105.0"
- },
- {
- "min": "0.106.0",
- "max": "3.0.2"
- },
- {
- "min": "4.0.0",
- "max": "4.0.3"
- },
- {
- "min": "5.0.0",
- "max": "< 6.0.0"
- },
- {
- "min": "6.0.0",
- "max": "< 8.0.0"
- },
- {
- "min": "8.0.0",
- "max": "< 15.0.0"
- },
- {
- "min": "15.0.0",
- "max": "< 18.0.0"
- }
-]
+[
+ {
+ "min": "0.54.0",
+ "max": "0.59.40"
+ },
+ {
+ "min": "0.61.0",
+ "max": "0.64.10"
+ },
+ {
+ "min": "0.66.0",
+ "max": "0.68.0"
+ },
+ {
+ "min": "0.69.0",
+ "max": "0.72.3"
+ },
+ {
+ "min": "0.72.4",
+ "max": "0.74.6"
+ },
+ {
+ "min": "0.75.0",
+ "max": "0.75.48"
+ },
+ {
+ "min": "0.76.0",
+ "max": "0.77.0"
+ },
+ {
+ "min": "0.78.0",
+ "max": "0.78.28"
+ },
+ {
+ "min": "0.79.0",
+ "max": "0.81.8"
+ },
+ {
+ "min": "0.82.0",
+ "max": "0.87.28"
+ },
+ {
+ "min": "0.88.0",
+ "max": "0.89.8"
+ },
+ {
+ "min": "0.90.0",
+ "max": "0.90.37"
+ },
+ {
+ "min": "0.91.0",
+ "max": "0.93.4"
+ },
+ {
+ "min": "0.94.0",
+ "max": "0.94.0"
+ },
+ {
+ "min": "0.95.0",
+ "max": "0.96.9"
+ },
+ {
+ "min": "0.98.0",
+ "max": "0.98.0"
+ },
+ {
+ "min": "0.99.0",
+ "max": "0.105.0"
+ },
+ {
+ "min": "0.106.0",
+ "max": "3.0.2"
+ },
+ {
+ "min": "4.0.0",
+ "max": "4.0.3"
+ },
+ {
+ "min": "5.0.0",
+ "max": "< 6.0.0"
+ },
+ {
+ "min": "6.0.0",
+ "max": "< 8.0.0"
+ },
+ {
+ "min": "8.0.0",
+ "max": "< 15.0.0"
+ },
+ {
+ "min": "15.0.0",
+ "max": "< 18.0.0"
+ }
+]
diff --git a/apps/plugins/lib/abilities.ts b/apps/plugins/lib/abilities.ts
index 52b12a07..96cccf43 100644
--- a/apps/plugins/lib/abilities.ts
+++ b/apps/plugins/lib/abilities.ts
@@ -1,79 +1,81 @@
-import { Abilities, User } from "@/lib/base";
-import {
- TeamMemberRoleSchema,
- TeamMembership,
- UserRoleSchema,
-} from "@/lib/prisma";
-import { TRPCError } from "@trpc/server";
-
-export function defineAbilitiesFor({
- user,
- teamMemberships,
-}: {
- user: User | null;
- teamMemberships: TeamMembership[] | null;
-}): Abilities {
- const isAdmin = user?.role === UserRoleSchema.Values.ADMIN;
-
- // == here is intentional
- const getTeamRole = async (teamId: string) =>
- teamMemberships?.find((m) => m.teamId == teamId)?.role ?? null;
-
- const isTeamOwner = async (teamId: string) =>
- isAdmin ||
- (await getTeamRole(teamId)) === TeamMemberRoleSchema.Values.OWNER;
-
- const isTeamMember = async (teamId: string) =>
- (await isTeamOwner(teamId)) ||
- (await getTeamRole(teamId)) === TeamMemberRoleSchema.Values.MEMBER;
-
- const requireTeamMember = async (teamId: string) => {
- if (!user) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "Authentication required",
- });
- }
-
- if (await isTeamMember(teamId)) {
- return;
- }
-
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Team not found",
- });
- };
-
- const requireTeamOwner = async (teamId: string) => {
- if (!user) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "Authentication required",
- });
- }
-
- if (await isTeamOwner(teamId)) {
- return;
- }
- if (await isTeamMember(teamId)) {
- throw new TRPCError({
- code: "FORBIDDEN",
- message: "Permission denied",
- });
- }
-
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Team not found",
- });
- };
-
- return {
- isAdmin,
- isTeamMember,
- isTeamOwner,
- requireTeamMember,
- requireTeamOwner,
- };
-}
+import { Abilities, User } from "@/lib/base";
+import {
+ TeamMemberRoleSchema,
+ TeamMembership,
+ UserRoleSchema,
+} from "@/lib/prisma";
+import { TRPCError } from "@trpc/server";
+
+export function defineAbilitiesFor({
+ user,
+ teamMemberships,
+}: {
+ user: User | null;
+
+ teamMemberships: TeamMembership[] | null;
+}): Abilities {
+ const isAdmin = user?.role === UserRoleSchema.Values.ADMIN;
+
+ // == here is intentional
+ const getTeamRole = async (teamId: string) =>
+ teamMemberships?.find((m) => m.teamId == teamId)?.role ?? null;
+
+ const isTeamOwner = async (teamId: string) =>
+ isAdmin ||
+ (await getTeamRole(teamId)) === TeamMemberRoleSchema.Values.OWNER;
+
+ const isTeamMember = async (teamId: string) =>
+ (await isTeamOwner(teamId)) ||
+ (await getTeamRole(teamId)) === TeamMemberRoleSchema.Values.MEMBER;
+
+ const requireTeamMember = async (teamId: string) => {
+ if (!user) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Authentication required",
+ });
+ }
+
+ if (await isTeamMember(teamId)) {
+ return;
+ }
+
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Team not found",
+ });
+ };
+
+ const requireTeamOwner = async (teamId: string) => {
+ if (!user) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Authentication required",
+ });
+ }
+
+ if (await isTeamOwner(teamId)) {
+ return;
+ }
+
+ if (await isTeamMember(teamId)) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Permission denied",
+ });
+ }
+
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Team not found",
+ });
+ };
+
+ return {
+ isAdmin,
+ isTeamMember,
+ isTeamOwner,
+ requireTeamMember,
+ requireTeamOwner,
+ };
+}
diff --git a/apps/plugins/lib/api/compatRange/router.ts b/apps/plugins/lib/api/compatRange/router.ts
index 1878accf..96fc2e63 100644
--- a/apps/plugins/lib/api/compatRange/router.ts
+++ b/apps/plugins/lib/api/compatRange/router.ts
@@ -1,382 +1,417 @@
-import { publicProcedure, router } from "@/lib/base";
-import { db } from "@/lib/prisma";
-import { TRPCError } from "@trpc/server";
-import semver from "semver";
-import { z } from "zod";
-import { VersionRange, VersionRangeSchema } from "./zod";
-
-export const CompatRangeSchema = z.object({
- id: z.bigint(),
- from: z.string(),
- to: z.string(),
-});
-
-export const compatRangeRouter = router({
- list: publicProcedure
- .input(z.void())
- .output(z.array(CompatRangeSchema))
- .query(async ({ ctx }) => {
- const versions = await db.compatRange.findMany({
- orderBy: {
- from: "asc",
- },
- });
-
- versions.sort((a, b) => {
- return semver.compare(b.from, a.from);
- });
-
- return versions;
- }),
-
- get: publicProcedure
- .input(
- z.object({
- id: z.bigint(),
- includePrerelease: z.boolean().default(false),
- })
- )
- .output(
- z.object({
- id: z.bigint(),
- from: z.string(),
- to: z.string(),
- plugins: z.array(VersionRangeSchema),
- runtimes: z.array(VersionRangeSchema),
- })
- )
- .query(async ({ ctx, input: { id, includePrerelease } }) => {
- const range = await db.compatRange.findUnique({
- where: {
- id: id,
- },
- select: {
- id: true,
- from: true,
- to: true,
- plugins: {
- where: {
- ...(includePrerelease
- ? {}
- : {
- version: {
- not: {
- contains: "-",
- },
- },
- }),
- },
- select: {
- id: true,
- version: true,
- plugin: {
- select: {
- name: true,
- },
- },
- },
- },
- runtimes: {
- where: {
- ...(includePrerelease
- ? {}
- : {
- version: {
- not: {
- contains: "-",
- },
- },
- }),
- },
- select: {
- id: true,
- version: true,
- runtime: {
- select: {
- name: true,
- },
- },
- },
- },
- },
- });
-
- if (!range) {
- throw new Error("CompatRange not found");
- }
- const plugins = merge(
- range.plugins.map((p) => ({
- name: p.plugin.name,
- version: p.version,
- }))
- );
- const runtimes = merge(
- range.runtimes.map((p) => ({
- name: p.runtime.name,
- version: p.version,
- }))
- );
-
- return {
- id: range.id,
- from: range.from,
- to: range.to,
- plugins,
- runtimes,
- };
- }),
-
- byPluginRunnerVersion: publicProcedure
- .input(
- z.object({
- version: z.string().describe("The version of the swc_plugin_runner"),
- })
- )
- .output(z.nullable(CompatRangeSchema))
- .query(async ({ ctx, input: { version } }) => {
- const v = await db.swcPluginRunnerVersion.findUnique({
- where: {
- version,
- },
- select: {
- compatRange: {
- select: {
- id: true,
- from: true,
- to: true,
- },
- },
- },
- });
-
- return v?.compatRange ?? null;
- }),
-
- byCoreVersion: publicProcedure
- .input(
- z.object({
- version: z.string(),
- })
- )
- .output(z.nullable(CompatRangeSchema))
- .query(async ({ ctx, input: { version } }) => {
- // Try the cache first.
- {
- const v = await db.swcCoreVersion.findUnique({
- where: {
- version,
- },
- select: {
- compatRange: {
- select: {
- id: true,
- from: true,
- to: true,
- },
- },
- },
- });
-
- if (v) {
- return v.compatRange;
- }
- }
-
- console.warn("Fallback to full search");
- const compatRanges = await db.compatRange.findMany({
- select: {
- id: true,
- from: true,
- to: true,
- },
- });
-
- for (const range of compatRanges) {
- if (matchRange(range, version)) {
- return range;
- }
- }
-
- return null;
- }),
-
- addCacheForCrates: publicProcedure
- .input(
- z.object({
- pluginRunnerVersions: z.array(z.string()),
- coreVersions: z.array(
- z.object({
- version: z.string().describe("The version of the swc_core"),
- pluginRunnerReq: z.string(),
- })
- ),
- })
- )
- .output(z.void())
- .mutation(
- async ({ ctx, input: { coreVersions, pluginRunnerVersions } }) => {
- if (process.env.NODE_ENV === "production") {
- throw new TRPCError({
- code: "FORBIDDEN",
- });
- }
-
- const previousMaxCoreVersion = await maxSwcCoreVersion();
- const previousMaxPluginRunnerVersion =
- await maxSwcPluginRunnerVersion();
-
- const compatRanges = await db.compatRange.findMany({
- select: {
- id: true,
- from: true,
- to: true,
- },
- });
-
- const done = new Set();
-
- function byVersion(swcCoreVersion: string) {
- for (const range of compatRanges) {
- if (matchRange(range, swcCoreVersion)) {
- return range;
- }
- }
- }
-
- for (const corePkg of coreVersions) {
- corePkg.version = corePkg.version.replace("v", "");
-
- if (semver.lt(corePkg.version, previousMaxCoreVersion)) {
- console.log(
- `Skipping swc_core@${corePkg.version} as it's less than previous max (${previousMaxCoreVersion})`
- );
- continue;
- }
-
- const compatRange = byVersion(corePkg.version);
-
- if (!compatRange) {
- console.error(`Compat range not found for ${corePkg.version}`);
- continue;
- }
-
- for (let rv of pluginRunnerVersions) {
- rv = rv.replace("v", "");
-
- if (done.has(rv)) {
- continue;
- }
- if (semver.lt(rv, previousMaxPluginRunnerVersion)) {
- continue;
- }
-
- if (semver.satisfies(rv, corePkg.pluginRunnerReq)) {
- await db.swcPluginRunnerVersion.upsert({
- where: {
- version: rv,
- },
- create: {
- version: rv,
- compatRangeId: compatRange.id,
- },
- update: {
- compatRangeId: compatRange.id,
- },
- });
- console.log(`Imported swc_plugin_runner@${rv}`);
- done.add(rv);
- }
- }
-
- await db.swcCoreVersion.upsert({
- where: {
- version: corePkg.version,
- },
- create: {
- version: corePkg.version,
- pluginRunnerReq: corePkg.pluginRunnerReq,
- compatRangeId: compatRange.id,
- },
- update: {
- pluginRunnerReq: corePkg.pluginRunnerReq,
- },
- });
- console.log(`Imported swc_core@${corePkg.version}`);
- }
- }
- ),
-});
-
-function merge(ranges: { name: string; version: string }[]): VersionRange[] {
- const merged: { [key: string]: VersionRange } = {};
-
- for (const { name, version } of ranges) {
- if (!merged[name]) {
- merged[name] = { name, minVersion: "0.0.0", maxVersion: "0.0.0" };
- }
-
- const { min, max } = mergeVersion(
- merged[name].minVersion,
- merged[name].maxVersion,
- version
- );
- merged[name] = { name, minVersion: min, maxVersion: max };
- }
-
- return Object.values(merged);
-}
-/**
- *
- * @param min semver
- * @param max semver
- * @param newValue semver
- */
-function mergeVersion(min: string, max: string, newValue: string) {
- const minVersion =
- min !== "0.0.0" && semver.lt(min, newValue) ? min : newValue;
- const maxVersion = semver.gt(max, newValue) ? max : newValue;
-
- return { min: minVersion, max: maxVersion };
-}
-
-async function maxSwcCoreVersion() {
- const coreVersions = await db.swcCoreVersion.findMany({
- select: {
- version: true,
- },
- });
-
- return coreVersions.reduce((max, core) => {
- return semver.gt(max, core.version) ? max : core.version;
- }, "0.0.0");
-}
-
-async function maxSwcPluginRunnerVersion() {
- const pluginRunnerVersions = await db.swcPluginRunnerVersion.findMany({
- select: {
- version: true,
- },
- });
-
- return pluginRunnerVersions.reduce((max, pluginRunner) => {
- return semver.gt(max, pluginRunner.version) ? max : pluginRunner.version;
- }, "0.0.0");
-}
-
-export function matchRange(
- range: { from: string; to: string },
- version: string
-): boolean {
- if (!semver.gte(version, range.from)) {
- return false;
- }
-
- try {
- if (semver.satisfies(version, range.to)) {
- return true;
- } else {
- return false;
- }
- } catch (ignored) {}
- return semver.lte(version, range.to);
-}
+import { publicProcedure, router } from "@/lib/base";
+import { db } from "@/lib/prisma";
+import { TRPCError } from "@trpc/server";
+import semver from "semver";
+import { z } from "zod";
+
+import { VersionRange, VersionRangeSchema } from "./zod";
+
+export const CompatRangeSchema = z.object({
+ id: z.bigint(),
+ from: z.string(),
+ to: z.string(),
+});
+
+export const compatRangeRouter = router({
+ list: publicProcedure
+ .input(z.void())
+ .output(z.array(CompatRangeSchema))
+ .query(async ({ ctx }) => {
+ const versions = await db.compatRange.findMany({
+ orderBy: {
+ from: "asc",
+ },
+ });
+
+ versions.sort((a, b) => {
+ return semver.compare(b.from, a.from);
+ });
+
+ return versions;
+ }),
+
+ get: publicProcedure
+ .input(
+ z.object({
+ id: z.bigint(),
+ includePrerelease: z.boolean().default(false),
+ }),
+ )
+ .output(
+ z.object({
+ id: z.bigint(),
+ from: z.string(),
+ to: z.string(),
+ plugins: z.array(VersionRangeSchema),
+ runtimes: z.array(VersionRangeSchema),
+ }),
+ )
+ .query(async ({ ctx, input: { id, includePrerelease } }) => {
+ const range = await db.compatRange.findUnique({
+ where: {
+ id: id,
+ },
+ select: {
+ id: true,
+ from: true,
+ to: true,
+ plugins: {
+ where: {
+ ...(includePrerelease
+ ? {}
+ : {
+ version: {
+ not: {
+ contains: "-",
+ },
+ },
+ }),
+ },
+ select: {
+ id: true,
+ version: true,
+ plugin: {
+ select: {
+ name: true,
+ },
+ },
+ },
+ },
+ runtimes: {
+ where: {
+ ...(includePrerelease
+ ? {}
+ : {
+ version: {
+ not: {
+ contains: "-",
+ },
+ },
+ }),
+ },
+ select: {
+ id: true,
+ version: true,
+ runtime: {
+ select: {
+ name: true,
+ },
+ },
+ },
+ },
+ },
+ });
+
+ if (!range) {
+ throw new Error("CompatRange not found");
+ }
+
+ const plugins = merge(
+ range.plugins.map((p) => ({
+ name: p.plugin.name,
+ version: p.version,
+ })),
+ );
+
+ const runtimes = merge(
+ range.runtimes.map((p) => ({
+ name: p.runtime.name,
+ version: p.version,
+ })),
+ );
+
+ return {
+ id: range.id,
+ from: range.from,
+ to: range.to,
+ plugins,
+ runtimes,
+ };
+ }),
+
+ byPluginRunnerVersion: publicProcedure
+ .input(
+ z.object({
+ version: z
+ .string()
+ .describe("The version of the swc_plugin_runner"),
+ }),
+ )
+ .output(z.nullable(CompatRangeSchema))
+ .query(async ({ ctx, input: { version } }) => {
+ const v = await db.swcPluginRunnerVersion.findUnique({
+ where: {
+ version,
+ },
+ select: {
+ compatRange: {
+ select: {
+ id: true,
+ from: true,
+ to: true,
+ },
+ },
+ },
+ });
+
+ return v?.compatRange ?? null;
+ }),
+
+ byCoreVersion: publicProcedure
+ .input(
+ z.object({
+ version: z.string(),
+ }),
+ )
+ .output(z.nullable(CompatRangeSchema))
+ .query(async ({ ctx, input: { version } }) => {
+ // Try the cache first.
+ {
+ const v = await db.swcCoreVersion.findUnique({
+ where: {
+ version,
+ },
+ select: {
+ compatRange: {
+ select: {
+ id: true,
+ from: true,
+ to: true,
+ },
+ },
+ },
+ });
+
+ for (const range of compatRanges) {
+ if (matchRange(range, version)) {
+ return range;
+ }
+ }
+
+ console.warn("Fallback to full search");
+
+ const compatRanges = await db.compatRange.findMany({
+ select: {
+ id: true,
+ from: true,
+ to: true,
+ },
+ });
+
+ for (const range of compatRanges) {
+ if (
+ semver.gte(version, range.from) &&
+ (range.to === "*" || semver.lte(version, range.to))
+ ) {
+ return range;
+ }
+ }
+
+ return null;
+ }),
+
+ addCacheForCrates: publicProcedure
+ .input(
+ z.object({
+ pluginRunnerVersions: z.array(z.string()),
+ coreVersions: z.array(
+ z.object({
+ version: z
+ .string()
+ .describe("The version of the swc_core"),
+ pluginRunnerReq: z.string(),
+ }),
+ ),
+ }),
+ )
+ .output(z.void())
+ .mutation(
+ async ({ ctx, input: { coreVersions, pluginRunnerVersions } }) => {
+ if (process.env.NODE_ENV === "production") {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ });
+ }
+
+ function byVersion(swcCoreVersion: string) {
+ for (const range of compatRanges) {
+ if (matchRange(range, swcCoreVersion)) {
+ return range;
+ }
+ }
+ }
+
+ const previousMaxPluginRunnerVersion =
+ await maxSwcPluginRunnerVersion();
+
+ const compatRanges = await db.compatRange.findMany({
+ select: {
+ id: true,
+ from: true,
+ to: true,
+ },
+ });
+
+ const done = new Set();
+
+ function byVersion(swcCoreVersion: string) {
+ for (const range of compatRanges) {
+ if (
+ semver.gte(swcCoreVersion, range.from) &&
+ (range.to === "*" ||
+ semver.lte(swcCoreVersion, range.to))
+ ) {
+ return range;
+ }
+ }
+ }
+
+ for (const corePkg of coreVersions) {
+ corePkg.version = corePkg.version.replace("v", "");
+
+ if (semver.lt(corePkg.version, previousMaxCoreVersion)) {
+ console.log(
+ `Skipping swc_core@${corePkg.version} as it's less than previous max (${previousMaxCoreVersion})`,
+ );
+
+ continue;
+ }
+
+ const compatRange = byVersion(corePkg.version);
+
+ if (!compatRange) {
+ console.error(
+ `Compat range not found for ${corePkg.version}`,
+ );
+
+ continue;
+ }
+
+ for (let rv of pluginRunnerVersions) {
+ rv = rv.replace("v", "");
+
+ if (done.has(rv)) {
+ continue;
+ }
+
+ if (semver.lt(rv, previousMaxPluginRunnerVersion)) {
+ continue;
+ }
+
+ if (semver.satisfies(rv, corePkg.pluginRunnerReq)) {
+ await db.swcPluginRunnerVersion.upsert({
+ where: {
+ version: rv,
+ },
+ create: {
+ version: rv,
+ compatRangeId: compatRange.id,
+ },
+ update: {
+ compatRangeId: compatRange.id,
+ },
+ });
+
+ console.log(`Imported swc_plugin_runner@${rv}`);
+
+ done.add(rv);
+ }
+ }
+
+ await db.swcCoreVersion.upsert({
+ where: {
+ version: corePkg.version,
+ },
+ create: {
+ version: corePkg.version,
+ pluginRunnerReq: corePkg.pluginRunnerReq,
+ compatRangeId: compatRange.id,
+ },
+ update: {
+ pluginRunnerReq: corePkg.pluginRunnerReq,
+ },
+ });
+
+ console.log(`Imported swc_core@${corePkg.version}`);
+ }
+ },
+ ),
+});
+
+function merge(ranges: { name: string; version: string }[]): VersionRange[] {
+ const merged: { [key: string]: VersionRange } = {};
+
+ for (const { name, version } of ranges) {
+ if (!merged[name]) {
+ merged[name] = { name, minVersion: "0.0.0", maxVersion: "0.0.0" };
+ }
+
+ const { min, max } = mergeVersion(
+ merged[name].minVersion,
+ merged[name].maxVersion,
+ version,
+ );
+
+ merged[name] = { name, minVersion: min, maxVersion: max };
+ }
+
+ return Object.values(merged);
+}
+/**
+ *
+ * @param min semver
+ * @param max semver
+ * @param newValue semver
+ */
+function mergeVersion(min: string, max: string, newValue: string) {
+ const minVersion =
+ min !== "0.0.0" && semver.lt(min, newValue) ? min : newValue;
+
+ const maxVersion = semver.gt(max, newValue) ? max : newValue;
+
+ return { min: minVersion, max: maxVersion };
+}
+
+async function maxSwcCoreVersion() {
+ const coreVersions = await db.swcCoreVersion.findMany({
+ select: {
+ version: true,
+ },
+ });
+
+ return coreVersions.reduce((max, core) => {
+ return semver.gt(max, core.version) ? max : core.version;
+ }, "0.0.0");
+}
+
+async function maxSwcPluginRunnerVersion() {
+ const pluginRunnerVersions = await db.swcPluginRunnerVersion.findMany({
+ select: {
+ version: true,
+ },
+ });
+
+ return pluginRunnerVersions.reduce((max, pluginRunner) => {
+ return semver.gt(max, pluginRunner.version)
+ ? max
+ : pluginRunner.version;
+ }, "0.0.0");
+}
+
+export function matchRange(
+ range: { from: string; to: string },
+ version: string
+): boolean {
+ if (!semver.gte(version, range.from)) {
+ return false;
+ }
+
+ try {
+ if (semver.satisfies(version, range.to)) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (ignored) {}
+ return semver.lte(version, range.to);
+}
diff --git a/apps/plugins/lib/api/compatRange/zod.ts b/apps/plugins/lib/api/compatRange/zod.ts
index eb282cd6..8d2f8960 100644
--- a/apps/plugins/lib/api/compatRange/zod.ts
+++ b/apps/plugins/lib/api/compatRange/zod.ts
@@ -1,8 +1,9 @@
-import { z } from "zod";
-
-export const VersionRangeSchema = z.object({
- name: z.string(),
- minVersion: z.string(),
- maxVersion: z.string(),
-});
-export type VersionRange = z.infer;
+import { z } from "zod";
+
+export const VersionRangeSchema = z.object({
+ name: z.string(),
+ minVersion: z.string(),
+ maxVersion: z.string(),
+});
+
+export type VersionRange = z.infer;
diff --git a/apps/plugins/lib/api/router.ts b/apps/plugins/lib/api/router.ts
index 6e68a6f5..1eb98c77 100644
--- a/apps/plugins/lib/api/router.ts
+++ b/apps/plugins/lib/api/router.ts
@@ -1,20 +1,22 @@
-import { router } from "@/lib/base";
-
-import { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
-import { compatRangeRouter } from "./compatRange/router";
-import { runtimeRouter } from "./runtimes/router";
-import { updaterRouter } from "./updater/router";
-import { userRouter } from "./users/router";
-
-export const apiRouter = router({
- users: userRouter,
-
- runtime: runtimeRouter,
- compatRange: compatRangeRouter,
-
- updater: updaterRouter,
-});
-
-export type ApiRouter = typeof apiRouter;
-export type ApiInput = inferRouterInputs;
-export type ApiOutput = inferRouterOutputs;
+import { router } from "@/lib/base";
+import { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
+
+import { compatRangeRouter } from "./compatRange/router";
+import { runtimeRouter } from "./runtimes/router";
+import { updaterRouter } from "./updater/router";
+import { userRouter } from "./users/router";
+
+export const apiRouter = router({
+ users: userRouter,
+
+ runtime: runtimeRouter,
+ compatRange: compatRangeRouter,
+
+ updater: updaterRouter,
+});
+
+export type ApiRouter = typeof apiRouter;
+
+export type ApiInput = inferRouterInputs;
+
+export type ApiOutput = inferRouterOutputs;
diff --git a/apps/plugins/lib/api/runtimes/router.ts b/apps/plugins/lib/api/runtimes/router.ts
index 723955a9..0121c295 100644
--- a/apps/plugins/lib/api/runtimes/router.ts
+++ b/apps/plugins/lib/api/runtimes/router.ts
@@ -1,61 +1,62 @@
-import { publicProcedure, router } from "@/lib/base";
-import { db } from "@/lib/prisma";
-import { compare } from "semver";
-import { z } from "zod";
-
-export const runtimeRouter = router({
- list: publicProcedure
- .input(z.void())
- .output(
- z.array(
- z.object({
- id: z.bigint(),
- name: z.string(),
- })
- )
- )
- .query(async () => {
- const runtimes = await db.swcRuntime.findMany({
- select: {
- id: true,
- name: true,
- },
- });
-
- return runtimes;
- }),
-
- listVersions: publicProcedure
- .input(
- z.object({
- runtimeId: z.bigint(),
- })
- )
- .output(
- z.array(
- z.object({
- version: z.string(),
- compatRangeId: z.bigint(),
- })
- )
- )
- .query(async ({ input: { runtimeId } }) => {
- const versions = await db.swcRuntimeVersion.findMany({
- where: {
- runtimeId,
- },
- select: {
- version: true,
- compatRangeId: true,
- },
- orderBy: {
- version: "desc",
- },
- });
- versions.sort((a, b) => {
- return compare(b.version, a.version);
- });
-
- return versions;
- }),
-});
+import { publicProcedure, router } from "@/lib/base";
+import { db } from "@/lib/prisma";
+import { compare } from "semver";
+import { z } from "zod";
+
+export const runtimeRouter = router({
+ list: publicProcedure
+ .input(z.void())
+ .output(
+ z.array(
+ z.object({
+ id: z.bigint(),
+ name: z.string(),
+ }),
+ ),
+ )
+ .query(async () => {
+ const runtimes = await db.swcRuntime.findMany({
+ select: {
+ id: true,
+ name: true,
+ },
+ });
+
+ return runtimes;
+ }),
+
+ listVersions: publicProcedure
+ .input(
+ z.object({
+ runtimeId: z.bigint(),
+ }),
+ )
+ .output(
+ z.array(
+ z.object({
+ version: z.string(),
+ compatRangeId: z.bigint(),
+ }),
+ ),
+ )
+ .query(async ({ input: { runtimeId } }) => {
+ const versions = await db.swcRuntimeVersion.findMany({
+ where: {
+ runtimeId,
+ },
+ select: {
+ version: true,
+ compatRangeId: true,
+ },
+ orderBy: {
+ version: "desc",
+ },
+ });
+
+ versions.sort((a, b) => {
+ return compare(b.version, a.version);
+ });
+
+ return versions;
+ }),
+});
diff --git a/apps/plugins/lib/api/server.ts b/apps/plugins/lib/api/server.ts
index d9bf70a9..0a9b537b 100644
--- a/apps/plugins/lib/api/server.ts
+++ b/apps/plugins/lib/api/server.ts
@@ -1,184 +1,191 @@
-import { getCurrentUser } from "@/lib/api/auth";
-import { apiRouter } from "@/lib/api/router";
-import {
- Abilities,
- Context,
- User,
- UserRoleSchema,
- createCallerFactory,
-} from "@/lib/base";
-import { TeamMemberRoleSchema, TeamMemberRoleType, db } from "@/lib/prisma";
-import { TRPCError } from "@trpc/server";
-import {
- FetchCreateContextFnOptions,
- fetchRequestHandler,
-} from "@trpc/server/adapters/fetch";
-import { memoize } from "lodash-es";
-import { headers } from "next/headers";
-
-export const handler = (req: Request) =>
- fetchRequestHandler({
- endpoint: "/api/trpc",
- router: apiRouter,
- req,
- createContext,
- });
-
-async function createContext(
- params?: FetchCreateContextFnOptions | { isAdmin: boolean }
-): Promise {
- if (!params) {
- console.warn("No params provided to createContext");
- return {
- async getAccessToken() {
- return undefined;
- },
- user: null,
- abilities: defineAbilitiesFor({ user: null }),
- responseHeaders: null,
- isAdmin: false,
- };
- }
-
- if ("isAdmin" in params) {
- return {
- async getAccessToken() {
- return undefined;
- },
- user: null,
- abilities: defineAbilitiesFor({ user: null }),
- responseHeaders: null,
- isAdmin: !!("isAdmin" in params && params.isAdmin),
- };
- }
-
- const user: User | null = await getCurrentUser();
-
- const abilities = defineAbilitiesFor({
- user,
- });
-
- return {
- async getAccessToken() {
- const h = await headers();
- const auth = h.get("authorization");
- return auth ? auth.replace("Bearer ", "") : undefined;
- },
- user,
- abilities,
- responseHeaders: null,
- isAdmin: false,
- };
-}
-
-export function defineAbilitiesFor({ user }: { user: User | null }): Abilities {
- const isAdmin = user?.role === UserRoleSchema.Values.ADMIN;
-
- // == here is intentional
- const getTeamRole = memoize(
- async (teamId: string): Promise => {
- if (!user) {
- return null;
- }
-
- const membership = await db.teamMembership.findUnique({
- where: {
- userPerTeam: {
- teamId: teamId,
- userId: user.id,
- },
- },
- select: {
- role: true,
- },
- });
-
- return membership?.role ?? null;
- }
- );
-
- const isTeamOwner = async (teamId: string) =>
- isAdmin ||
- (await getTeamRole(teamId)) === TeamMemberRoleSchema.Values.OWNER;
-
- const isTeamMember = async (teamId: string) =>
- (await isTeamOwner(teamId)) ||
- (await getTeamRole(teamId)) === TeamMemberRoleSchema.Values.MEMBER;
-
- const requireTeamMember = async (teamId: string) => {
- if (!user) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "Authentication required",
- });
- }
-
- if (await isTeamMember(teamId)) {
- return;
- }
-
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Team not found",
- });
- };
-
- const requireTeamOwner = async (teamId: string) => {
- if (!user) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "Authentication required",
- });
- }
-
- if (await isTeamOwner(teamId)) {
- return;
- }
- if (await isTeamMember(teamId)) {
- throw new TRPCError({
- code: "FORBIDDEN",
- message: "Permission denied",
- });
- }
-
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Team not found",
- });
- };
-
- return {
- isAdmin,
- isTeamMember,
- isTeamOwner,
- requireTeamMember,
- requireTeamOwner,
- };
-}
-
-const factory = createCallerFactory(apiRouter);
-
-export const createCaller = async (ctx?: Context) => {
- if (ctx) {
- return factory(ctx);
- }
-
- const user: User | null = await getCurrentUser();
-
- const abilities = defineAbilitiesFor({
- user,
- });
-
- const newCtx: Context = {
- async getAccessToken() {
- const h = await headers();
- const auth = h.get("authorization");
- return auth ? auth.replace("Bearer ", "") : undefined;
- },
- user,
- abilities,
- responseHeaders: null,
- isAdmin: false,
- };
- return factory(newCtx);
-};
+import { getCurrentUser } from "@/lib/api/auth";
+import { apiRouter } from "@/lib/api/router";
+import {
+ Abilities,
+ Context,
+ createCallerFactory,
+ User,
+ UserRoleSchema,
+} from "@/lib/base";
+import { db, TeamMemberRoleSchema, TeamMemberRoleType } from "@/lib/prisma";
+import { TRPCError } from "@trpc/server";
+import {
+ FetchCreateContextFnOptions,
+ fetchRequestHandler,
+} from "@trpc/server/adapters/fetch";
+import { memoize } from "lodash-es";
+import { headers } from "next/headers";
+
+export const handler = (req: Request) =>
+ fetchRequestHandler({
+ endpoint: "/api/trpc",
+ router: apiRouter,
+ req,
+ createContext,
+ });
+
+async function createContext(
+ params?: FetchCreateContextFnOptions | { isAdmin: boolean },
+): Promise {
+ if (!params) {
+ console.warn("No params provided to createContext");
+ return {
+ async getAccessToken() {
+ return undefined;
+ },
+ user: null,
+ abilities: defineAbilitiesFor({ user: null }),
+ responseHeaders: null,
+ isAdmin: false,
+ };
+ }
+
+ if ("isAdmin" in params) {
+ return {
+ async getAccessToken() {
+ return undefined;
+ },
+ user: null,
+ abilities: defineAbilitiesFor({ user: null }),
+ responseHeaders: null,
+ isAdmin: !!("isAdmin" in params && params.isAdmin),
+ };
+ }
+
+ if ("isAdmin" in params) {
+ return {
+ getAccessToken: () => undefined,
+ user: null,
+ abilities: defineAbilitiesFor({ user: null }),
+ responseHeaders: null,
+ isAdmin: !!("isAdmin" in params && params.isAdmin),
+ };
+ }
+
+ const user: User | null = await getCurrentUser();
+
+ return {
+ async getAccessToken() {
+ const h = await headers();
+ const auth = h.get("authorization");
+ return auth ? auth.replace("Bearer ", "") : undefined;
+ },
+ user,
+ abilities,
+ responseHeaders: null,
+ isAdmin: false,
+ };
+}
+
+export function defineAbilitiesFor({ user }: { user: User | null }): Abilities {
+ const isAdmin = user?.role === UserRoleSchema.Values.ADMIN;
+
+ // == here is intentional
+ const getTeamRole = memoize(
+ async (teamId: string): Promise => {
+ if (!user) {
+ return null;
+ }
+
+ const membership = await db.teamMembership.findUnique({
+ where: {
+ userPerTeam: {
+ teamId: teamId,
+ userId: user.id,
+ },
+ },
+ select: {
+ role: true,
+ },
+ });
+
+ return membership?.role ?? null;
+ },
+ );
+
+ const isTeamOwner = async (teamId: string) =>
+ isAdmin ||
+ (await getTeamRole(teamId)) === TeamMemberRoleSchema.Values.OWNER;
+
+ const isTeamMember = async (teamId: string) =>
+ (await isTeamOwner(teamId)) ||
+ (await getTeamRole(teamId)) === TeamMemberRoleSchema.Values.MEMBER;
+
+ const requireTeamMember = async (teamId: string) => {
+ if (!user) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Authentication required",
+ });
+ }
+
+ if (await isTeamMember(teamId)) {
+ return;
+ }
+
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Team not found",
+ });
+ };
+
+ const requireTeamOwner = async (teamId: string) => {
+ if (!user) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Authentication required",
+ });
+ }
+
+ if (await isTeamOwner(teamId)) {
+ return;
+ }
+
+ if (await isTeamMember(teamId)) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Permission denied",
+ });
+ }
+
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Team not found",
+ });
+ };
+
+ return {
+ isAdmin,
+ isTeamMember,
+ isTeamOwner,
+ requireTeamMember,
+ requireTeamOwner,
+ };
+}
+
+const factory = createCallerFactory(apiRouter);
+
+export const createCaller = async (ctx?: Context) => {
+ if (ctx) {
+ return factory(ctx);
+ }
+
+ const user: User | null = await getCurrentUser();
+
+ const abilities = defineAbilitiesFor({
+ user,
+ });
+
+ const newCtx: Context = {
+ async getAccessToken() {
+ const h = await headers();
+ const auth = h.get("authorization");
+ return auth ? auth.replace("Bearer ", "") : undefined;
+ },
+ user,
+ abilities,
+ responseHeaders: null,
+ isAdmin: false,
+ };
+ return factory(newCtx);
+};
diff --git a/apps/plugins/lib/api/updater/router.ts b/apps/plugins/lib/api/updater/router.ts
index 0eb94047..566b85e1 100644
--- a/apps/plugins/lib/api/updater/router.ts
+++ b/apps/plugins/lib/api/updater/router.ts
@@ -1,168 +1,176 @@
-import { publicProcedure, router } from "@/lib/base";
-import { db } from "@/lib/prisma";
-import { TRPCError } from "@trpc/server";
-import semver from "semver";
-import { z } from "zod";
-import { matchRange } from "../compatRange/router";
-
-function validateToken(token: string) {
- if (token === process.env.CRAWL_SECRET) {
- return;
- }
-
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "Invalid token",
- });
-}
-
-const PackageVersionSchema = z.object({
- version: z.string(),
- swcCoreVersion: z.string(),
-});
-
-const PackageSchema = z.object({
- name: z.string(),
- versions: z.array(PackageVersionSchema),
-});
-
-export const UpdateWasmPluginsInputSchema = z.object({
- token: z.string(),
- pkgs: z.array(PackageSchema),
-});
-
-export const UpdateRuntimesInputSchema = z.object({
- token: z.string(),
- pkgs: z.array(PackageSchema),
-});
-
-export const updaterRouter = router({
- updateWasmPlugins: publicProcedure
- .input(UpdateWasmPluginsInputSchema)
- .output(z.void())
- .mutation(async ({ input, ctx }) => {
- validateToken(input.token);
-
- const api = await (await import("@/lib/api/server")).createCaller(ctx);
-
- for (const pkg of input.pkgs) {
- try {
- const plugin = await db.swcPlugin.upsert({
- where: {
- name: pkg.name,
- },
- create: {
- name: pkg.name,
- },
- update: {},
- });
-
- for (const version of pkg.versions) {
- const swcCoreVersion = version.swcCoreVersion;
- const compatRange = await api.compatRange.byCoreVersion({
- version: swcCoreVersion,
- });
-
- if (!compatRange) {
- console.error(
- `Compat range not found for SWC core version ${swcCoreVersion}`
- );
- continue;
- }
-
- await db.swcPluginVersion.upsert({
- where: {
- pluginId_version: {
- pluginId: plugin.id,
- version: version.version,
- },
- },
- create: {
- pluginId: plugin.id,
- version: version.version,
- compatRangeId: compatRange.id,
- swcCoreVersion,
- },
- update: {
- compatRangeId: compatRange.id,
- swcCoreVersion,
- },
- });
- }
- } catch (e) {
- console.error(`Error updating wasm plugins for ${pkg.name}`, e);
- }
- }
- }),
-
- updateRuntimes: publicProcedure
- .input(UpdateRuntimesInputSchema)
- .output(z.void())
- .mutation(async ({ input, ctx }) => {
- validateToken(input.token);
-
- const compatRanges = await db.compatRange.findMany({
- select: {
- id: true,
- from: true,
- to: true,
- },
- });
-
- // Runtimes has so many versions so we need a faster logic.
- function byVersion(swcCoreVersion: string) {
- for (const range of compatRanges) {
- if (matchRange(range, swcCoreVersion)) {
- return range;
- }
- }
- }
-
- for (const pkg of input.pkgs) {
- try {
- const runtime = await db.swcRuntime.upsert({
- where: {
- name: pkg.name,
- },
- create: {
- name: pkg.name,
- },
- update: {},
- });
-
- for (const version of pkg.versions) {
- const swcCoreVersion = version.swcCoreVersion;
- const compatRange = byVersion(swcCoreVersion);
-
- if (!compatRange) {
- console.error(
- `Compat range not found for SWC core version ${swcCoreVersion}`
- );
- continue;
- }
-
- await db.swcRuntimeVersion.upsert({
- where: {
- runtimeId_version: {
- runtimeId: runtime.id,
- version: version.version,
- },
- },
- create: {
- runtimeId: runtime.id,
- version: version.version,
- compatRangeId: compatRange.id,
- swcCoreVersion,
- },
- update: {
- compatRangeId: compatRange.id,
- swcCoreVersion,
- },
- });
- }
- } catch (e) {
- console.error(`Error updating runtimes for ${pkg.name}`, e);
- }
- }
- }),
-});
+import { publicProcedure, router } from "@/lib/base";
+import { db } from "@/lib/prisma";
+import { TRPCError } from "@trpc/server";
+import semver from "semver";
+import { z } from "zod";
+
+import { matchRange } from "../compatRange/router";
+
+function validateToken(token: string) {
+ if (token === process.env.CRAWL_SECRET) {
+ return;
+ }
+
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Invalid token",
+ });
+}
+
+const PackageVersionSchema = z.object({
+ version: z.string(),
+ swcCoreVersion: z.string(),
+});
+
+const PackageSchema = z.object({
+ name: z.string(),
+ versions: z.array(PackageVersionSchema),
+});
+
+export const UpdateWasmPluginsInputSchema = z.object({
+ token: z.string(),
+ pkgs: z.array(PackageSchema),
+});
+
+export const UpdateRuntimesInputSchema = z.object({
+ token: z.string(),
+ pkgs: z.array(PackageSchema),
+});
+
+export const updaterRouter = router({
+ updateWasmPlugins: publicProcedure
+ .input(UpdateWasmPluginsInputSchema)
+ .output(z.void())
+ .mutation(async ({ input, ctx }) => {
+ validateToken(input.token);
+
+ const api = await (
+ await import("@/lib/api/server")
+ ).createCaller(ctx);
+
+ for (const pkg of input.pkgs) {
+ try {
+ const plugin = await db.swcPlugin.upsert({
+ where: {
+ name: pkg.name,
+ },
+ create: {
+ name: pkg.name,
+ },
+ update: {},
+ });
+
+ for (const version of pkg.versions) {
+ const swcCoreVersion = version.swcCoreVersion;
+ const compatRange = await api.compatRange.byCoreVersion(
+ {
+ version: swcCoreVersion,
+ },
+ );
+
+ if (!compatRange) {
+ console.error(
+ `Compat range not found for SWC core version ${swcCoreVersion}`,
+ );
+ continue;
+ }
+
+ await db.swcPluginVersion.upsert({
+ where: {
+ pluginId_version: {
+ pluginId: plugin.id,
+ version: version.version,
+ },
+ },
+ create: {
+ pluginId: plugin.id,
+ version: version.version,
+ compatRangeId: compatRange.id,
+ swcCoreVersion,
+ },
+ update: {
+ compatRangeId: compatRange.id,
+ swcCoreVersion,
+ },
+ });
+ }
+ } catch (e) {
+ console.error(
+ `Error updating wasm plugins for ${pkg.name}`,
+ e,
+ );
+ }
+ }
+ }),
+
+ updateRuntimes: publicProcedure
+ .input(UpdateRuntimesInputSchema)
+ .output(z.void())
+ .mutation(async ({ input, ctx }) => {
+ validateToken(input.token);
+
+ const compatRanges = await db.compatRange.findMany({
+ select: {
+ id: true,
+ from: true,
+ to: true,
+ },
+ });
+
+ // Runtimes has so many versions so we need a faster logic.
+ function byVersion(swcCoreVersion: string) {
+ for (const range of compatRanges) {
+ if (matchRange(range, swcCoreVersion)) {
+ return range;
+ }
+ }
+ }
+
+ for (const pkg of input.pkgs) {
+ try {
+ const runtime = await db.swcRuntime.upsert({
+ where: {
+ name: pkg.name,
+ },
+ create: {
+ name: pkg.name,
+ },
+ update: {},
+ });
+
+ for (const version of pkg.versions) {
+ const swcCoreVersion = version.swcCoreVersion;
+ const compatRange = byVersion(swcCoreVersion);
+
+ if (!compatRange) {
+ console.error(
+ `Compat range not found for SWC core version ${swcCoreVersion}`,
+ );
+ continue;
+ }
+
+ await db.swcRuntimeVersion.upsert({
+ where: {
+ runtimeId_version: {
+ runtimeId: runtime.id,
+ version: version.version,
+ },
+ },
+ create: {
+ runtimeId: runtime.id,
+ version: version.version,
+ compatRangeId: compatRange.id,
+ swcCoreVersion,
+ },
+ update: {
+ compatRangeId: compatRange.id,
+ swcCoreVersion,
+ },
+ });
+ }
+ } catch (e) {
+ console.error(`Error updating runtimes for ${pkg.name}`, e);
+ }
+ }
+ }),
+});
diff --git a/apps/plugins/lib/api/users/router.ts b/apps/plugins/lib/api/users/router.ts
index b5cacbca..f775a2c7 100644
--- a/apps/plugins/lib/api/users/router.ts
+++ b/apps/plugins/lib/api/users/router.ts
@@ -1,11 +1,11 @@
-import { protectedProcedure, router } from "@/lib/base";
-import { z } from "zod";
-
-export const userRouter = router({
- me: protectedProcedure
- .input(z.void())
- .output(z.object({}))
- .query(({ ctx }) => {
- return ctx.user;
- }),
-});
+import { protectedProcedure, router } from "@/lib/base";
+import { z } from "zod";
+
+export const userRouter = router({
+ me: protectedProcedure
+ .input(z.void())
+ .output(z.object({}))
+ .query(({ ctx }) => {
+ return ctx.user;
+ }),
+});
diff --git a/apps/plugins/lib/auth.ts b/apps/plugins/lib/auth.ts
index 638a4146..86161f1f 100644
--- a/apps/plugins/lib/auth.ts
+++ b/apps/plugins/lib/auth.ts
@@ -1,12 +1,12 @@
-import { db } from "@/lib/prisma";
-import { PrismaAdapter } from "@auth/prisma-adapter";
-import NextAuth from "next-auth";
-import Google from "next-auth/providers/google";
-import Passkey from "next-auth/providers/passkey";
-
-export const { handlers, signIn, signOut, auth } = NextAuth({
- adapter: PrismaAdapter(db),
- providers: [Google, Passkey],
-
- experimental: { enableWebAuthn: true },
-});
+import { db } from "@/lib/prisma";
+import { PrismaAdapter } from "@auth/prisma-adapter";
+import NextAuth from "next-auth";
+import Google from "next-auth/providers/google";
+import Passkey from "next-auth/providers/passkey";
+
+export const { handlers, signIn, signOut, auth } = NextAuth({
+ adapter: PrismaAdapter(db),
+ providers: [Google, Passkey],
+
+ experimental: { enableWebAuthn: true },
+});
diff --git a/apps/plugins/lib/auth/user-context.tsx b/apps/plugins/lib/auth/user-context.tsx
index 4b121fec..b82b3212 100644
--- a/apps/plugins/lib/auth/user-context.tsx
+++ b/apps/plugins/lib/auth/user-context.tsx
@@ -1,120 +1,120 @@
-"use client";
-
-import { ApiOutput } from "@/lib/api/router";
-import { apiClient } from "@/lib/trpc/web-client";
-import { useRouter } from "next/navigation";
-import { PropsWithChildren, createContext, useEffect, useState } from "react";
-
-type User = ApiOutput["users"]["me"] | null;
-// TODO
-// type TeamMembership = NonNullable;
-type TeamMembership = any;
-
-type UserContext = {
- user: User;
- reloadUser: () => Promise;
- logout: () => Promise;
- loaded: boolean;
- teamId: string | null;
- teamSlug: string | "_";
- teamMembership: TeamMembership | null;
-};
-
-const authBroadcastChannel = new BroadcastChannel("auth");
-type AuthEvent = {
- type: "loaded" | "logout";
- user: User | null;
-};
-
-export const userContext = createContext({
- user: null,
- reloadUser: () => Promise.resolve(),
- logout: () => Promise.resolve(),
- loaded: false,
- teamSlug: "_",
- teamId: null,
- teamMembership: null,
-});
-
-export function UserContextProvider({
- children,
- initialUser,
- teamMembership,
- teamSlug,
-}: PropsWithChildren<{
- initialUser: User;
- teamMembership?: TeamMembership;
- teamSlug?: string;
-}>) {
- const router = useRouter();
- const [loaded, setLoaded] = useState(!!initialUser);
- const [user, setUser] = useState(initialUser);
- const userQuery = apiClient.users.me.useQuery(undefined, {
- refetchOnWindowFocus: false,
- refetchOnMount: false,
- enabled: !initialUser,
- });
-
- const reloadUser = async () => {
- await userQuery.refetch();
- };
-
- const logout = async () => {
- router.replace("/api/auth/signout");
- };
-
- useEffect(() => {
- if (userQuery.data) setUser(userQuery.data);
- }, [userQuery.data]);
-
- useEffect(() => {
- if (userQuery.isSuccess) setLoaded(true);
- }, [userQuery.isSuccess]);
-
- useEffect(() => {
- if (user && loaded)
- authBroadcastChannel.postMessage({
- type: "loaded",
- user: user,
- });
- }, [user, loaded]);
-
- useEffect(() => {
- const handleAuthEvent = async (event: MessageEvent) => {
- if (JSON.stringify(event.data.user) !== JSON.stringify(user)) {
- if (event.data.type === "logout") {
- //eslint-disable-next-line
- userQuery.refetch();
- setUser(null);
- router.replace("/");
- } else {
- setUser(event.data.user);
- }
- }
- };
-
- authBroadcastChannel.addEventListener("message", handleAuthEvent);
-
- return () =>
- authBroadcastChannel.removeEventListener(
- "message",
- handleAuthEvent
- );
- }, [router, user, userQuery]);
-
- return (
-
- {children}
-
- );
-}
+"use client";
+
+import { ApiOutput } from "@/lib/api/router";
+import { apiClient } from "@/lib/trpc/web-client";
+import { useRouter } from "next/navigation";
+import { PropsWithChildren, createContext, useEffect, useState } from "react";
+
+type User = ApiOutput["users"]["me"] | null;
+// TODO
+// type TeamMembership = NonNullable;
+type TeamMembership = any;
+
+type UserContext = {
+ user: User;
+ reloadUser: () => Promise;
+ logout: () => Promise;
+ loaded: boolean;
+ teamId: string | null;
+ teamSlug: string | "_";
+ teamMembership: TeamMembership | null;
+};
+
+const authBroadcastChannel = new BroadcastChannel("auth");
+type AuthEvent = {
+ type: "loaded" | "logout";
+ user: User | null;
+};
+
+export const userContext = createContext({
+ user: null,
+ reloadUser: () => Promise.resolve(),
+ logout: () => Promise.resolve(),
+ loaded: false,
+ teamSlug: "_",
+ teamId: null,
+ teamMembership: null,
+});
+
+export function UserContextProvider({
+ children,
+ initialUser,
+ teamMembership,
+ teamSlug,
+}: PropsWithChildren<{
+ initialUser: User;
+ teamMembership?: TeamMembership;
+ teamSlug?: string;
+}>) {
+ const router = useRouter();
+ const [loaded, setLoaded] = useState(!!initialUser);
+ const [user, setUser] = useState(initialUser);
+ const userQuery = apiClient.users.me.useQuery(undefined, {
+ refetchOnWindowFocus: false,
+ refetchOnMount: false,
+ enabled: !initialUser,
+ });
+
+ const reloadUser = async () => {
+ await userQuery.refetch();
+ };
+
+ const logout = async () => {
+ router.replace("/api/auth/signout");
+ };
+
+ useEffect(() => {
+ if (userQuery.data) setUser(userQuery.data);
+ }, [userQuery.data]);
+
+ useEffect(() => {
+ if (userQuery.isSuccess) setLoaded(true);
+ }, [userQuery.isSuccess]);
+
+ useEffect(() => {
+ if (user && loaded)
+ authBroadcastChannel.postMessage({
+ type: "loaded",
+ user: user,
+ });
+ }, [user, loaded]);
+
+ useEffect(() => {
+ const handleAuthEvent = async (event: MessageEvent) => {
+ if (JSON.stringify(event.data.user) !== JSON.stringify(user)) {
+ if (event.data.type === "logout") {
+ //eslint-disable-next-line
+ userQuery.refetch();
+ setUser(null);
+ router.replace("/");
+ } else {
+ setUser(event.data.user);
+ }
+ }
+ };
+
+ authBroadcastChannel.addEventListener("message", handleAuthEvent);
+
+ return () =>
+ authBroadcastChannel.removeEventListener(
+ "message",
+ handleAuthEvent
+ );
+ }, [router, user, userQuery]);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/plugins/lib/base/index.ts b/apps/plugins/lib/base/index.ts
index 7804f0e6..bcdb0a39 100644
--- a/apps/plugins/lib/base/index.ts
+++ b/apps/plugins/lib/base/index.ts
@@ -1,133 +1,146 @@
-import "server-only";
-
-import { TRPCError, initTRPC } from "@trpc/server";
-import { AsyncLocalStorage } from "async_hooks";
-import superjson from "superjson";
-import { z } from "zod";
-
-export { default as superjson } from "superjson";
-
-export const UserRoleSchema = z.enum(["USER", "ADMIN"]);
-
-export type UserRoleType = `${z.infer}`;
-
-export const UserSchema = z.object({
- role: UserRoleSchema,
- id: z.string(),
- email: z.string(),
- emailVerified: z.date().nullable(),
- name: z.string().nullable(),
- image: z.string().nullable(),
-});
-
-export type User = z.infer;
-
-export type Abilities = {
- isAdmin: boolean;
- isTeamMember: (teamId: string) => Promise;
- isTeamOwner: (teamId: string) => Promise;
- requireTeamMember: (teamId: string) => Promise;
- requireTeamOwner: (teamId: string) => Promise;
-};
-
-export type Context = {
- /**
- * Get the access token from the request header. (Authorization: Bearer $token)
- */
- getAccessToken(): Promise;
-
- user: User | null;
- abilities: Abilities;
- responseHeaders: Headers | null;
- isAdmin: boolean;
-};
-
-export const t = initTRPC.context().create({
- transformer: superjson,
-});
-
-/**
- * Create a router
- * @see https://trpc.io/docs/router
- */
-export const router = t.router;
-
-export const createCallerFactory = t.createCallerFactory;
-
-/**
- * Create an unprotected procedure
- * @see https://trpc.io/docs/procedures
- **/
-export const publicProcedure = t.procedure.use(async (opts) => {
- const start = Date.now();
-
- const result = await contextStore.run(
- opts.ctx,
- async () => await opts.next()
- );
-
- if (result.ok) {
- // Drop 'undefined'
- result.data = removeUndefinedRecursively(result.data);
- }
-
- const durationMs = Date.now() - start;
- const meta = { path: opts.path, type: opts.type, durationMs };
-
- if (process.env.NODE_ENV !== "test") {
- result.ok
- ? console.log("OK request timing:", meta)
- : console.error("Non-OK request timing", meta);
-
- if (!result.ok) {
- console.error("Error:", result.error);
- }
- }
-
- return result;
-});
-
-/**
- * @see https://trpc.io/docs/merging-routers
- */
-export const mergeRouters = t.mergeRouters;
-
-export const middleware = t.middleware;
-
-const isAuthenticated = t.middleware(({ ctx, next }) => {
- if (!ctx.user) throw new TRPCError({ code: "UNAUTHORIZED" });
- return next({
- ctx: {
- ...ctx,
- user: ctx.user,
- },
- });
-});
-
-export const protectedProcedure = publicProcedure.use(isAuthenticated);
-
-export const contextStore = new AsyncLocalStorage();
-
-function removeUndefinedRecursively(data: T): NonNullable | null {
- if (data === null || data === undefined) return null;
- if (typeof data !== "object") {
- return data;
- }
-
- if (Array.isArray(data)) {
- return data.map(removeUndefinedRecursively) as NonNullable;
- }
-
- if (data instanceof Date) return data;
-
- const obj: any = data;
- const newObj: any = {};
- Object.keys(data).forEach((key) => {
- if (obj[key] === undefined) return;
-
- const value = removeUndefinedRecursively(obj[key]);
-
- newObj[key] = value;
- });
- return newObj;
-}
+import "server-only";
+
+import { AsyncLocalStorage } from "async_hooks";
+import { initTRPC, TRPCError } from "@trpc/server";
+import superjson from "superjson";
+import { z } from "zod";
+
+export { default as superjson } from "superjson";
+
+export const UserRoleSchema = z.enum(["USER", "ADMIN"]);
+
+export type UserRoleType = `${z.infer}`;
+
+export const UserSchema = z.object({
+ role: UserRoleSchema,
+ id: z.string(),
+ email: z.string(),
+ emailVerified: z.date().nullable(),
+ name: z.string().nullable(),
+ image: z.string().nullable(),
+});
+
+export type User = z.infer;
+
+export type Abilities = {
+ isAdmin: boolean;
+
+ isTeamMember: (teamId: string) => Promise;
+
+ isTeamOwner: (teamId: string) => Promise;
+
+ requireTeamMember: (teamId: string) => Promise;
+
+ requireTeamOwner: (teamId: string) => Promise