diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8311655..def6e58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,20 +6,15 @@ jobs: runs-on: ubuntu-latest permissions: - id-token: write contents: read + id-token: write steps: - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v2 - with: - deno-version: v2.x + - name: Install dependencies + run: npm install - name: Run tests - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: deno test --allow-env --allow-read=. --allow-net=api.github.com,0.0.0.0,localhost - - name: Deploy to Deno Deploy - uses: denoland/deployctl@v1 - with: - project: dprint-plugins - entrypoint: main.ts + run: npx vitest run + - name: Deploy to Cloudflare Workers + if: github.ref == 'refs/heads/main' + uses: cloudflare/wrangler-action@v3 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..188edee --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.claude +node_modules +.wrangler +.dev.vars diff --git a/README.md b/README.md index 0c63939..c5efd4e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,9 @@ Website for [https://plugins.dprint.dev](https://plugins.dprint.dev). -This website is hosted on [Deno Deploy](https://deno.com/deploy). - ## Redirect to Any GitHub Repo (For Plugin Authors) -This service provides a convenient redirect for a plugin stored in any GitHub repo. +This service provides a convenient URL for a plugin stored in any GitHub repo. To use it, create a GitHub release in your repo with: @@ -28,8 +26,27 @@ Restrictions and recommendations: If your repo name is in the format `dprint-plugin-` then you can omit `dprint-plugin-` in the `` for the redirect (ex. `https://plugins.dprint.dev//dprint-plugin-typescript-0.0.0.wasm` may be shortened to `https://plugins.dprint.dev//typescript-0.0.0.wasm`). +## Release Assets + +For approved repositories, individual release assets can be served directly via: + +``` +https://plugins.dprint.dev////asset/ +``` + +For example: + +``` +https://plugins.dprint.dev/dprint/dprint-plugin-prettier/0.67.0/asset/dprint-plugin-prettier-x86_64-apple-darwin.zip +``` + +This is useful for process plugins that distribute platform-specific binaries. Assets are cached in R2 for persistence. + +Note: To get approved, open a PR to this repository including your username and plugin repo. + ## Run Locally ```bash -deno run --allow-read=. --allow-net --allow-env --no-check main.ts +npm install +npm run dev ``` diff --git a/deno.json b/deno.json deleted file mode 100644 index 94e489e..0000000 --- a/deno.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "imports": { - "@std/assert": "jsr:@std/assert@^1.0.12", - "preact": "https://esm.sh/preact@10.15.1?pin=v135", - "preact/": "https://esm.sh/preact@10.15.1&pin=v135/", - "preact-render-to-string": "https://esm.sh/*preact-render-to-string@6.1.0?pin=v135" - } -} diff --git a/deno.lock b/deno.lock deleted file mode 100644 index 0c9387f..0000000 --- a/deno.lock +++ /dev/null @@ -1,29 +0,0 @@ -{ - "version": "4", - "specifiers": { - "jsr:@std/assert@^1.0.12": "1.0.12", - "jsr:@std/internal@^1.0.6": "1.0.6" - }, - "jsr": { - "@std/assert@1.0.12": { - "integrity": "08009f0926dda9cbd8bef3a35d3b6a4b964b0ab5c3e140a4e0351fbf34af5b9a", - "dependencies": [ - "jsr:@std/internal" - ] - }, - "@std/internal@1.0.6": { - "integrity": "9533b128f230f73bd209408bb07a4b12f8d4255ab2a4d22a1fd6d87304aca9a4" - } - }, - "remote": { - "https://esm.sh/*preact-render-to-string@6.1.0?pin=v135": "ce1c6902880f66372096778d76a4afcb1e7f10a7349939c146d5e33aee5962dd", - "https://esm.sh/preact@10.15.1?pin=v135": "796ac5b6d7b4c71b1eba2e60dee47d3a7c639d99b546b35902af6405e845cf5a", - "https://esm.sh/stable/preact@10.15.1/denonext/preact.mjs": "30710ac1d5ff3711ae0c04eddbeb706f34f82d97489f61aaf09897bc75d2a628", - "https://esm.sh/v135/preact-render-to-string@6.1.0/X-ZS8q/denonext/preact-render-to-string.mjs": "8ce71d44f2a197f8fafa24c8d1ac19e0d7fac5a171efae6154f8d754e1ae0ae4" - }, - "workspace": { - "dependencies": [ - "jsr:@std/assert@^1.0.12" - ] - } -} diff --git a/dprint.json b/dprint.json index 281ae36..d4628ba 100644 --- a/dprint.json +++ b/dprint.json @@ -7,11 +7,11 @@ "**/*-lock.json" ], "plugins": [ - "https://plugins.dprint.dev/typescript-0.88.7.wasm", - "https://plugins.dprint.dev/json-0.19.1.wasm", - "https://plugins.dprint.dev/markdown-0.16.3.wasm", - "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.3.0.wasm", - "https://plugins.dprint.dev/g-plane/malva-v0.5.1.wasm", - "https://plugins.dprint.dev/g-plane/markup_fmt-v0.10.0.wasm" + "https://plugins.dprint.dev/typescript-0.95.15.wasm", + "https://plugins.dprint.dev/json-0.21.3.wasm", + "https://plugins.dprint.dev/markdown-0.21.1.wasm", + "https://plugins.dprint.dev/g-plane/pretty_yaml-v0.6.0.wasm", + "https://plugins.dprint.dev/g-plane/malva-v0.15.2.wasm", + "https://plugins.dprint.dev/g-plane/markup_fmt-v0.27.0.wasm" ] } diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..cc6cba3 --- /dev/null +++ b/env.d.ts @@ -0,0 +1,14 @@ +declare module "*.css" { + const content: string; + export default content; +} + +declare module "*.txt" { + const content: string; + export default content; +} + +interface Env { + DPRINT_PLUGINS_GH_TOKEN?: string; + PLUGIN_CACHE: R2Bucket; +} diff --git a/handleRequest.test.ts b/handleRequest.test.ts index 63ece30..1524a61 100644 --- a/handleRequest.test.ts +++ b/handleRequest.test.ts @@ -1,371 +1,361 @@ -import { assertEquals } from "@std/assert"; -import { createRequestHandler } from "./handleRequest.ts"; -import { RealClock } from "./utils/clock.ts"; +import { expect, it } from "vitest"; +import { createRequestHandler, resolvePluginOrSchemaUrl } from "./handleRequest.js"; -const connInfo: Deno.ServeHandlerInfo = { - remoteAddr: { - transport: "tcp", - hostname: "127.0.0.1", - port: 80, - }, - completed: Promise.resolve(), -}; - -Deno.test("should get info.json", async () => { - const { handleRequest } = createRequestHandler(new RealClock()); +it("should get info.json", async () => { + const { handleRequest } = createRequestHandler(); const response = await handleRequest( new Request("https://plugins.dprint.dev/info.json"), - connInfo, ); - assertEquals(response.headers.get("content-type"), "application/json; charset=utf-8"); - const data = await response.json(); + expect(response.headers.get("content-type")).toEqual("application/json; charset=utf-8"); + const data: any = await response.json(); for (const pluginData of data.latest) { - assertEquals(typeof pluginData.name, "string"); - assertEquals(typeof pluginData.version, "string"); - assertEquals(typeof pluginData.url, "string"); + expect(typeof pluginData.name).toEqual("string"); + expect(typeof pluginData.version).toEqual("string"); + expect(typeof pluginData.url).toEqual("string"); } }); -Deno.test("should get cli.json", async () => { - const { handleRequest } = createRequestHandler(new RealClock()); +it("should get cli.json", async () => { + const { handleRequest } = createRequestHandler(); const response = await handleRequest( new Request("https://plugins.dprint.dev/cli.json"), - connInfo, ); - assertEquals(response.headers.get("content-type"), "application/json; charset=utf-8"); - const data = await response.json(); - assertEquals(typeof data.version, "string"); + expect(response.headers.get("content-type")).toEqual("application/json; charset=utf-8"); + const data: any = await response.json(); + expect(typeof data.version).toEqual("string"); }); -async function getRedirectUrl(url: string) { - const { handleRequest } = createRequestHandler(new RealClock()); - const response = await handleRequest( - new Request(url), - connInfo, - ); - assertEquals(response.status, 302); - return response.headers.get("location")!; +async function resolveUrl(url: string) { + return await resolvePluginOrSchemaUrl(new URL(url)); } -Deno.test("should get correct info for typescript resolver", async () => { - function getWasmRedirectUrl(version: string) { - return getRedirectUrl(`https://plugins.dprint.dev/typescript-${version}.wasm`); - } - - assertEquals( - await getWasmRedirectUrl("0.19.4"), +it("should resolve typescript URLs", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/typescript-0.19.4.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/0.19.4/typescript-0.19.4.wasm", ); - assertEquals( - await getWasmRedirectUrl("0.44.0"), + expect( + await resolveUrl("https://plugins.dprint.dev/typescript-0.44.0.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/0.44.0/typescript-0.44.0.wasm", ); // file name changed here - assertEquals( - await getWasmRedirectUrl("0.44.1"), + expect( + await resolveUrl("https://plugins.dprint.dev/typescript-0.44.1.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/0.44.1/typescript.wasm", ); - assertEquals( - await getRedirectUrl(`https://plugins.dprint.dev/schemas/typescript-0.52.1.json`), + expect( + await resolveUrl("https://plugins.dprint.dev/schemas/typescript-0.52.1.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/0.52.1/schema.json", ); // file name changed after this - assertEquals( - await getWasmRedirectUrl("0.62.1"), + expect( + await resolveUrl("https://plugins.dprint.dev/typescript-0.62.1.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/0.62.1/typescript.wasm", ); - assertEquals( - await getWasmRedirectUrl("0.62.2"), + expect( + await resolveUrl("https://plugins.dprint.dev/typescript-0.62.2.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/0.62.2/plugin.wasm", ); }); -Deno.test("should get correct info for json resolver", async () => { - function getWasmRedirectUrl(version: string) { - return getRedirectUrl(`https://plugins.dprint.dev/json-${version}.wasm`); - } - - assertEquals( - await getWasmRedirectUrl("0.4.0"), +it("should resolve json URLs", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/json-0.4.0.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-json/releases/download/0.4.0/json-0.4.0.wasm", ); - assertEquals( - await getWasmRedirectUrl("0.10.1"), + expect( + await resolveUrl("https://plugins.dprint.dev/json-0.10.1.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-json/releases/download/0.10.1/json-0.10.1.wasm", ); // file name changed here - assertEquals( - await getWasmRedirectUrl("0.10.2"), + expect( + await resolveUrl("https://plugins.dprint.dev/json-0.10.2.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-json/releases/download/0.10.2/json.wasm", ); - assertEquals( - await getRedirectUrl(`https://plugins.dprint.dev/schemas/json-0.13.1.json`), + expect( + await resolveUrl("https://plugins.dprint.dev/schemas/json-0.13.1.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-json/releases/download/0.13.1/schema.json", ); // file name changed after this - assertEquals( - await getWasmRedirectUrl("0.14.0"), + expect( + await resolveUrl("https://plugins.dprint.dev/json-0.14.0.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-json/releases/download/0.14.0/json.wasm", ); - assertEquals( - await getWasmRedirectUrl("0.14.1"), + expect( + await resolveUrl("https://plugins.dprint.dev/json-0.14.1.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-json/releases/download/0.14.1/plugin.wasm", ); }); -Deno.test("should get correct info for markdown resolver", async () => { - function getWasmRedirectUrl(version: string) { - return getRedirectUrl(`https://plugins.dprint.dev/markdown-${version}.wasm`); - } - - assertEquals( - await getWasmRedirectUrl("0.1.0"), +it("should resolve markdown URLs", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/markdown-0.1.0.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-markdown/releases/download/0.1.0/markdown-0.1.0.wasm", ); - assertEquals( - await getWasmRedirectUrl("0.7.0"), + expect( + await resolveUrl("https://plugins.dprint.dev/markdown-0.7.0.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-markdown/releases/download/0.7.0/markdown-0.7.0.wasm", ); // file name changed here - assertEquals( - await getWasmRedirectUrl("0.7.1"), + expect( + await resolveUrl("https://plugins.dprint.dev/markdown-0.7.1.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-markdown/releases/download/0.7.1/markdown.wasm", ); - assertEquals( - await getRedirectUrl(`https://plugins.dprint.dev/schemas/markdown-0.10.0.json`), + expect( + await resolveUrl("https://plugins.dprint.dev/schemas/markdown-0.10.0.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-markdown/releases/download/0.10.0/schema.json", ); // file name changed after this - assertEquals( - await getWasmRedirectUrl("0.12.1"), + expect( + await resolveUrl("https://plugins.dprint.dev/markdown-0.12.1.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-markdown/releases/download/0.12.1/markdown.wasm", ); - assertEquals( - await getWasmRedirectUrl("0.12.2"), + expect( + await resolveUrl("https://plugins.dprint.dev/markdown-0.12.2.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-markdown/releases/download/0.12.2/plugin.wasm", ); }); -Deno.test("should get correct info for toml resolver", async () => { - function getWasmRedirectUrl(version: string) { - return getRedirectUrl(`https://plugins.dprint.dev/toml-${version}.wasm`); - } - - assertEquals( - await getWasmRedirectUrl("0.1.2"), +it("should resolve toml URLs", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/toml-0.1.2.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-toml/releases/download/0.1.2/toml.wasm", ); - assertEquals( - await getRedirectUrl(`https://plugins.dprint.dev/schemas/toml-0.5.0.json`), + expect( + await resolveUrl("https://plugins.dprint.dev/schemas/toml-0.5.0.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-toml/releases/download/0.5.0/schema.json", ); // file name changed after this - assertEquals( - await getWasmRedirectUrl("0.5.3"), + expect( + await resolveUrl("https://plugins.dprint.dev/toml-0.5.3.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-toml/releases/download/0.5.3/toml.wasm", ); - assertEquals( - await getWasmRedirectUrl("0.5.4"), + expect( + await resolveUrl("https://plugins.dprint.dev/toml-0.5.4.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-toml/releases/download/0.5.4/plugin.wasm", ); }); -Deno.test("should get correct info for dockerfile resolver", async () => { - function getWasmRedirectUrl(version: string) { - return getRedirectUrl(`https://plugins.dprint.dev/dockerfile-${version}.wasm`); - } - - assertEquals( - await getWasmRedirectUrl("0.1.0"), +it("should resolve dockerfile URLs", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/dockerfile-0.1.0.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-dockerfile/releases/download/0.1.0/dockerfile.wasm", ); - assertEquals( - await getRedirectUrl(`https://plugins.dprint.dev/schemas/dockerfile-0.1.0.json`), + expect( + await resolveUrl("https://plugins.dprint.dev/schemas/dockerfile-0.1.0.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-dockerfile/releases/download/0.1.0/schema.json", ); // file name changed after this - assertEquals( - await getWasmRedirectUrl("0.2.1"), + expect( + await resolveUrl("https://plugins.dprint.dev/dockerfile-0.2.1.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-dockerfile/releases/download/0.2.1/dockerfile.wasm", ); - assertEquals( - await getWasmRedirectUrl("0.2.2"), + expect( + await resolveUrl("https://plugins.dprint.dev/dockerfile-0.2.2.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-dockerfile/releases/download/0.2.2/plugin.wasm", ); }); -Deno.test("should get correct info for sql resolver", async () => { - function getWasmRedirectUrl(version: string) { - return getRedirectUrl(`https://plugins.dprint.dev/sql-${version}.wasm`); - } - - assertEquals( - await getWasmRedirectUrl("0.1.1"), +it("should resolve sql URLs", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/sql-0.1.1.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-sql/releases/download/0.1.1/sql.wasm", ); - assertEquals( - await getRedirectUrl(`https://plugins.dprint.dev/schemas/sql-0.1.1.json`), + expect( + await resolveUrl("https://plugins.dprint.dev/schemas/sql-0.1.1.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-sql/releases/download/0.1.1/schema.json", ); // file name changed here - assertEquals( - await getWasmRedirectUrl("0.1.2"), + expect( + await resolveUrl("https://plugins.dprint.dev/sql-0.1.2.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-sql/releases/download/0.1.2/plugin.wasm", ); }); -Deno.test("should get correct info for prettier resolver", async () => { - function getProcessPluginRedirectUrl(version: string, ext = "json") { - return getRedirectUrl(`https://plugins.dprint.dev/prettier-${version}.${ext}`); - } - +it("should resolve prettier URLs", async () => { // file name changed after this - assertEquals( - await getProcessPluginRedirectUrl("0.5.0", "exe-plugin"), + expect( + await resolveUrl("https://plugins.dprint.dev/prettier-0.5.0.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-prettier/releases/download/0.5.0/prettier.exe-plugin", ); - assertEquals( - await getProcessPluginRedirectUrl("0.5.1", "exe-plugin"), + expect( + await resolveUrl("https://plugins.dprint.dev/prettier-0.5.1.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-prettier/releases/download/0.5.1/plugin.exe-plugin", ); - // and changed again here - assertEquals( - await getProcessPluginRedirectUrl("0.6.2", "exe-plugin"), + expect( + await resolveUrl("https://plugins.dprint.dev/prettier-0.6.2.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-prettier/releases/download/0.6.2/plugin.exe-plugin", ); - assertEquals( - await getProcessPluginRedirectUrl("0.7.0"), + expect( + await resolveUrl("https://plugins.dprint.dev/prettier-0.7.0.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-prettier/releases/download/0.7.0/plugin.json", ); }); -Deno.test("should get correct info for roslyn resolver", async () => { - function getProcessPluginRedirectUrl(version: string, ext = "json") { - return getRedirectUrl(`https://plugins.dprint.dev/roslyn-${version}.${ext}`); - } - +it("should resolve roslyn URLs", async () => { // file name changed after this - assertEquals( - await getProcessPluginRedirectUrl("0.4.0", "exe-plugin"), + expect( + await resolveUrl("https://plugins.dprint.dev/roslyn-0.4.0.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-roslyn/releases/download/0.4.0/roslyn.exe-plugin", ); - assertEquals( - await getProcessPluginRedirectUrl("0.5.0", "exe-plugin"), + expect( + await resolveUrl("https://plugins.dprint.dev/roslyn-0.5.0.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-roslyn/releases/download/0.5.0/plugin.exe-plugin", ); // and again here - assertEquals( - await getProcessPluginRedirectUrl("0.6.4"), + expect( + await resolveUrl("https://plugins.dprint.dev/roslyn-0.6.4.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-roslyn/releases/download/0.6.4/plugin.json", ); }); -Deno.test("should get correct info for rustfmt resolver", async () => { - function getProcessPluginRedirectUrl(version: string, ext = "json") { - return getRedirectUrl(`https://plugins.dprint.dev/rustfmt-${version}.${ext}`); - } - +it("should resolve rustfmt URLs", async () => { // file name changed after this - assertEquals( - await getProcessPluginRedirectUrl("0.4.0", "exe-plugin"), + expect( + await resolveUrl("https://plugins.dprint.dev/rustfmt-0.4.0.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-rustfmt/releases/download/0.4.0/rustfmt.exe-plugin", ); - assertEquals( - await getProcessPluginRedirectUrl("0.5.1", "exe-plugin"), + expect( + await resolveUrl("https://plugins.dprint.dev/rustfmt-0.5.1.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-rustfmt/releases/download/0.5.1/plugin.exe-plugin", ); // and again here - assertEquals( - await getProcessPluginRedirectUrl("0.6.2"), + expect( + await resolveUrl("https://plugins.dprint.dev/rustfmt-0.6.2.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-rustfmt/releases/download/0.6.2/plugin.json", ); }); -Deno.test("should get correct info for yapf resolver", async () => { - function getProcessPluginRedirectUrl(version: string) { - return getRedirectUrl(`https://plugins.dprint.dev/yapf-${version}.exe-plugin`); - } - +it("should resolve yapf URLs", async () => { // file name changed after this - assertEquals( - await getProcessPluginRedirectUrl("0.2.0"), + expect( + await resolveUrl("https://plugins.dprint.dev/yapf-0.2.0.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-yapf/releases/download/0.2.0/yapf.exe-plugin", ); - assertEquals( - await getProcessPluginRedirectUrl("0.2.1"), + expect( + await resolveUrl("https://plugins.dprint.dev/yapf-0.2.1.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-yapf/releases/download/0.2.1/plugin.exe-plugin", ); }); -Deno.test("should get correct info for exec resolver", async () => { - function getProcessPluginRedirectUrl(version: string) { - return getRedirectUrl(`https://plugins.dprint.dev/exec-${version}.exe-plugin`); - } - - assertEquals( - await getProcessPluginRedirectUrl("0.1.0"), +it("should resolve exec URLs", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/exec-0.1.0.exe-plugin"), + ).toEqual( "https://github.com/dprint/dprint-plugin-exec/releases/download/0.1.0/plugin.exe-plugin", ); }); -Deno.test("tryResolvePluginUrl", async () => { - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/typescript-1.2.3.wasm"), +it("tryResolvePluginUrl", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/typescript-1.2.3.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/1.2.3/plugin.wasm", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/dprint/typescript-1.2.3.wasm"), + expect( + await resolveUrl("https://plugins.dprint.dev/dprint/typescript-1.2.3.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/1.2.3/plugin.wasm", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/dprint/dprint-plugin-typescript-1.2.3.wasm"), + expect( + await resolveUrl("https://plugins.dprint.dev/dprint/dprint-plugin-typescript-1.2.3.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/1.2.3/plugin.wasm", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/dprint/dprint-plugin-typescript-latest.wasm"), + expect( + await resolveUrl("https://plugins.dprint.dev/dprint/dprint-plugin-typescript-latest.wasm"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/latest/download/plugin.wasm", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/dprint/dprint-plugin-exec-0.3.0.json"), + expect( + await resolveUrl("https://plugins.dprint.dev/dprint/dprint-plugin-exec-0.3.0.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-exec/releases/download/0.3.0/plugin.json", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/lucacasonato/mf2-tools-0.1.0.wasm"), + expect( + await resolveUrl("https://plugins.dprint.dev/lucacasonato/mf2-tools-0.1.0.wasm"), + ).toEqual( "https://github.com/lucacasonato/mf2-tools/releases/download/0.1.0/dprint-plugin-mf2.wasm", ); }); // todo: mock github api for these tests -Deno.test("tryResolveSchemaUrl", async () => { - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/dprint/typescript/1.2.3/schema.json"), +it("tryResolveSchemaUrl", async () => { + expect( + await resolveUrl("https://plugins.dprint.dev/dprint/typescript/1.2.3/schema.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/1.2.3/schema.json", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/dprint/dprint-plugin-typescript/1.2.3/schema.json"), + expect( + await resolveUrl("https://plugins.dprint.dev/dprint/dprint-plugin-typescript/1.2.3/schema.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/download/1.2.3/schema.json", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/dprint/dprint-plugin-typescript/latest/schema.json"), + expect( + await resolveUrl("https://plugins.dprint.dev/dprint/dprint-plugin-typescript/latest/schema.json"), + ).toEqual( "https://github.com/dprint/dprint-plugin-typescript/releases/latest/download/schema.json", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/dprint/non-existent/1.2.3/schema.json"), + expect( + await resolveUrl("https://plugins.dprint.dev/dprint/non-existent/1.2.3/schema.json"), + ).toEqual( "https://github.com/dprint/non-existent/releases/download/1.2.3/schema.json", ); - assertEquals( - await getRedirectUrl("https://plugins.dprint.dev/lucacasonato/mf2-tools/0.1.0/schema.json"), + expect( + await resolveUrl("https://plugins.dprint.dev/lucacasonato/mf2-tools/0.1.0/schema.json"), + ).toEqual( "https://github.com/lucacasonato/mf2-tools/releases/download/0.1.0/dprint-plugin-mf2.schema.json", ); }); diff --git a/handleRequest.ts b/handleRequest.ts index 4b19dfe..a1e712d 100644 --- a/handleRequest.ts +++ b/handleRequest.ts @@ -1,9 +1,14 @@ -import { renderHome } from "./home.tsx"; +import { renderHome } from "./home.jsx"; import oldMappings from "./old_redirects.json" with { type: "json" }; -import { tryResolveLatestJson, tryResolvePluginUrl, tryResolveSchemaUrl } from "./plugins.ts"; -import { readInfoFile } from "./readInfoFile.ts"; -import { Clock } from "./utils/clock.ts"; -import { createFetchCacher, getCliInfo } from "./utils/mod.ts"; +import { tryResolveAssetUrl, tryResolveLatestJson, tryResolvePluginUrl, tryResolveSchemaUrl } from "./plugins.js"; +import { readInfoFile } from "./readInfoFile.js"; +import robotsTxt from "./robots.txt"; +import styleCSS from "./style.css"; +import { LruCache } from "./utils/LruCache.js"; +import { getCliInfo } from "./utils/mod.js"; +import { r2Get, r2Put } from "./utils/r2Cache.js"; + +const MAX_MEM_CACHE_BODY_SIZE = 10 * 1024 * 1024; // 10MB const contentTypes = { css: "text/css; charset=utf-8", @@ -11,26 +16,27 @@ const contentTypes = { json: "application/json; charset=utf-8", plain: "text/plain; charset=utf-8", wasm: "application/wasm", + octetStream: "application/octet-stream", }; -export function createRequestHandler(clock: Clock) { - const { fetchCached } = createFetchCacher(clock); +export function createRequestHandler() { + const memoryCache = new LruCache({ size: 50 }); return { - async handleRequest(request: Request, info: Deno.ServeHandlerInfo) { + async handleRequest(request: Request, ctx?: ExecutionContext) { const url = new URL(request.url); - const newUrl = await resolvePluginOrSchemaUrl(url); - if (newUrl != null) { - const contentType = newUrl.endsWith(".json") + const assetUrl = tryResolveAssetUrl(url); + if (assetUrl != null) { + return servePlugin(request, assetUrl, contentTypes.octetStream, ctx); + } + + const githubUrl = await resolvePluginOrSchemaUrl(url); + if (githubUrl != null) { + const contentType = githubUrl.endsWith(".json") || githubUrl.endsWith(".exe-plugin") ? contentTypes.json - : newUrl.endsWith(".wasm") + : githubUrl.endsWith(".wasm") ? contentTypes.wasm : contentTypes.plain; - return handleConditionalRedirectRequest({ - request, - url: newUrl, - contentType, - hostname: info.remoteAddr.hostname, - }); + return servePlugin(request, githubUrl, contentType, ctx); } const userLatestInfo = await tryResolveLatestJson(url); @@ -60,15 +66,20 @@ export function createRequestHandler(clock: Clock) { return createJsonResponse(JSON.stringify(cliInfo, null, 2), request); } + if (url.pathname === "/robots.txt") { + return new Response(robotsTxt, { + headers: { "content-type": contentTypes.plain }, + status: 200, + }); + } + if (url.pathname === "/style.css") { - return Deno.readTextFile("./style.css").then((text) => - new Response(text, { - headers: { - "content-type": "text/css; charset=utf-8", - }, - status: 200, - }) - ); + return new Response(styleCSS, { + headers: { + "content-type": contentTypes.css, + }, + status: 200, + }); } if (url.pathname === "/") { @@ -93,43 +104,72 @@ export function createRequestHandler(clock: Clock) { }, }; - // This is done to allow the playground to access these files - function handleConditionalRedirectRequest(params: { - request: Request; - url: string; - contentType: string; - hostname: string; - }) { - if (shouldDirectlyServeFile(params.request)) { - return fetchCached(params) - .then((result) => { - if (result.kind === "error") { - return result.response; - } - - return new Response(result.body, { - headers: { - "content-type": params.contentType, - // allow the playground to download this - "Access-Control-Allow-Origin": getAccessControlAllowOrigin( - params.request, - ), - }, - status: 200, - }); - }).catch((err) => { - console.error(err); - return new Response("Internal Server Error", { - status: 500, - }); - }); + async function servePlugin(request: Request, githubUrl: string, contentType: string, ctx?: ExecutionContext) { + try { + const body = await resolveBody(githubUrl, contentType, ctx); + return new Response(body, { + headers: { + "content-type": contentType, + "Access-Control-Allow-Origin": getAccessControlAllowOrigin(request), + }, + status: 200, + }); + } catch (err) { + console.error("Error serving plugin:", err); + return new Response("Internal Server Error", { status: 500 }); + } + } + + async function resolveBody( + githubUrl: string, + contentType: string, + ctx?: ExecutionContext, + ): Promise { + // L1: in-memory cache + const cached = memoryCache.get(githubUrl); + if (cached != null) { + return cached; + } + + // L2: R2 + const r2Object = await r2Get(githubUrl); + if (r2Object != null) { + // small enough for L1 — buffer and cache + if (r2Object.size <= MAX_MEM_CACHE_BODY_SIZE) { + const buffer = await r2Object.arrayBuffer(); + memoryCache.set(githubUrl, buffer); + return buffer; + } + // large — stream directly without buffering + return r2Object.body; + } + + // L3: fetch from GitHub + const response = await fetch(githubUrl, { + headers: { "user-agent": "dprint-plugins" }, + }); + if (!response.ok) { + throw new Error(`GitHub fetch failed: ${response.status} ${response.statusText}`); + } + + const body = await response.arrayBuffer(); + + // populate caches + const r2Promise = r2Put(githubUrl, body, contentType); + if (ctx != null) { + ctx.waitUntil(r2Promise); } else { - return createRedirectResponse(params.url); + await r2Promise; + } + if (body.byteLength <= MAX_MEM_CACHE_BODY_SIZE) { + memoryCache.set(githubUrl, body); } + + return body; } } -async function resolvePluginOrSchemaUrl(url: URL) { +export async function resolvePluginOrSchemaUrl(url: URL) { return (oldMappings as { [oldUrl: string]: string })[url.toString()] ?? await tryResolvePluginUrl(url) ?? await tryResolveSchemaUrl(url); @@ -146,35 +186,10 @@ function isLocalHostname(hostname: string) { return hostname === "localhost" || hostname === "127.0.0.1"; } -function shouldDirectlyServeFile(request: Request) { - // directly serve for when Deno makes a request in order to fix the content type - if (request.headers.get("user-agent")?.startsWith("Deno/")) { - return true; - } - - const origin = request.headers.get("origin"); - if (origin == null) { - return false; - } - - const hostname = new URL(origin).hostname; - return isLocalHostname(hostname) || hostname === "dprint.dev"; -} - -function createRedirectResponse(location: string) { - return new Response(null, { - headers: { - location, - }, - status: 302, // temporary redirect - }); -} - function createJsonResponse(text: string, request: Request) { return new Response(text, { headers: { "content-type": contentTypes.json, - // allow the dprint website to download this file "Access-Control-Allow-Origin": getAccessControlAllowOrigin(request), }, status: 200, diff --git a/home.tsx b/home.tsx index 9134273..35d3f9a 100644 --- a/home.tsx +++ b/home.tsx @@ -1,7 +1,5 @@ -/** @jsx h */ -import { h } from "preact"; import { renderToString } from "preact-render-to-string"; -import { PluginData, PluginsData, readInfoFile } from "./readInfoFile.ts"; +import { PluginData, PluginsData, readInfoFile } from "./readInfoFile.js"; export async function renderHome() { const content = await renderContent(); @@ -85,7 +83,7 @@ function renderPlugin(plugin: PluginData) {
{plugin.url}
{plugin.downloadCount.allVersions?.toLocaleString("en-US")}
-
diff --git a/main.ts b/main.ts index 3b5b02f..9bd0337 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,9 @@ -import { createRequestHandler } from "./handleRequest.ts"; -import { RealClock } from "./utils/clock.ts"; +import { createRequestHandler } from "./handleRequest.js"; -const { handleRequest } = createRequestHandler(new RealClock()); +const { handleRequest } = createRequestHandler(); -Deno.serve((request, info) => handleRequest(request, info)); +export default { + fetch(request: Request, env: unknown, ctx: ExecutionContext) { + return handleRequest(request, ctx); + }, +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..67d38c7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2756 @@ +{ + "name": "dprint-plugins", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dprint-plugins", + "dependencies": { + "preact": "^10.29.0", + "preact-render-to-string": "^6.6.6" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.13.4", + "@cloudflare/workers-types": "^4.20260317.1", + "vitest": "~4.1.1", + "wrangler": "^4.77.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.2.tgz", + "integrity": "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.0.tgz", + "integrity": "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/vitest-pool-workers": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/@cloudflare/vitest-pool-workers/-/vitest-pool-workers-0.13.4.tgz", + "integrity": "sha512-0LWc3d14+8yiQsb6Io8HqWmHcNTPUAbxuvt5KbeL3ZPY70xvvaFJdxMYuHPYVbVVvV2oakoyc3pRitJLZRmEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cjs-module-lexer": "^1.2.3", + "esbuild": "0.27.3", + "miniflare": "4.20260317.2", + "wrangler": "4.77.0", + "zod": "^3.25.76" + }, + "peerDependencies": { + "@vitest/runner": "^4.1.0", + "@vitest/snapshot": "^4.1.0", + "vitest": "^4.1.0" + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260317.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260317.1.tgz", + "integrity": "sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20260317.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260317.1.tgz", + "integrity": "sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260317.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260317.1.tgz", + "integrity": "sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260317.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260317.1.tgz", + "integrity": "sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260317.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260317.1.tgz", + "integrity": "sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workers-types": { + "version": "4.20260317.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260317.1.tgz", + "integrity": "sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peer": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz", + "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", + "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", + "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-SJ+/g+xNnOh6NqYxD0V3uVN4W3VfnrGsC9/hoglicgTNfABFG9JjISvkkU0dNY84MNHLWyOgxP9v9Y9pX4S7+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-7WQgR8SfOPwmDZGFkThUvsmd/nwAWv91oCO4I5LS7RKrssPZmOt7jONN0cW17ydGC1n/+puol1IpoieKqQidmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-39Ks6UvIHq4rEogIfQBoBRusj0Q0nPVWIvqmwBLaT6aqQGIakHdESBVOPRRLacy4WwUPIx4ZKzfZ9PMW+IeyUQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.11.tgz", + "integrity": "sha512-jfsm0ZHfhiqrvWjJAmzsqiIFPz5e7mAoCOPBNTcNgkiid/LaFKiq92+0ojH+nmJmKYkre4t71BWXUZDNp7vsag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.11.tgz", + "integrity": "sha512-zjQaUtSyq1nVe3nxmlSCuR96T1LPlpvmJ0SZy0WJFEsV4kFbXcq2u68L4E6O0XeFj4aex9bEauqjW8UQBeAvfQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-WMW1yE6IOnehTcFE9eipFkm3XN63zypWlrJQ2iF7NrQ9b2LDRjumFoOGJE8RJJTJCTBAdmLMnJ8uVitACUUo1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-jfndI9tsfm4APzjNt6QdBkYwre5lRPUgHeDHoI7ydKUuJvz3lZeCfMsI56BZj+7BYqiKsJm7cfd/6KYV7ubrBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-ZlFgw46NOAGMgcdvdYwAGu2Q+SLFA9LzbJLW+iyMOJyhj5wk6P3KEE9Gct4xWwSzFoPI7JCdYmYMzVtlgQ+zfw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-hIOYmuT6ofM4K04XAZd3OzMySEO4K0/nc9+jmNcxNAxRi6c5UWpqfw3KMFV4MVFWL+jQsSh+bGw2VqmaPMTLyw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.11.tgz", + "integrity": "sha512-qXBQQO9OvkjjQPLdUVr7Nr2t3QTZI7s4KZtfw7HzBgjbmAPSFwSv4rmET9lLSgq3rH/ndA3ngv3Qb8l2njoPNA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.11.tgz", + "integrity": "sha512-/tpFfoSTzUkH9LPY+cYbqZBDyyX62w5fICq9qzsHLL8uTI6BHip3Q9Uzft0wylk/i8OOwKik8OxW+QAhDmzwmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.11.tgz", + "integrity": "sha512-mcp3Rio2w72IvdZG0oQ4bM2c2oumtwHfUfKncUM6zGgz0KgPz4YmDPQfnXEiY5t3+KD/i8HG2rOB/LxdmieK2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.11.tgz", + "integrity": "sha512-LXk5Hii1Ph9asuGRjBuz8TUxdc1lWzB7nyfdoRgI0WGPZKmCxvlKk8KfYysqtr4MfGElu/f/pEQRh8fcEgkrWw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-dDwf5otnx0XgRY1yqxOC4ITizcdzS/8cQ3goOWv3jFAo4F+xQYni+hnMuO6+LssHHdJW7+OCVL3CoU4ycnh35Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.11.tgz", + "integrity": "sha512-LN4/skhSggybX71ews7dAj6r2geaMJfm3kMbK2KhFMg9B10AZXnKoLCVVgzhMHL0S+aKtr4p8QbAW8k+w95bAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.11.tgz", + "integrity": "sha512-xQO9vbwBecJRv9EUcQ/y0dzSTJgA7Q6UVN7xp6B81+tBGSLVAK03yJ9NkJaUA7JFD91kbjxRSC/mDnmvXzbHoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.15", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.15.tgz", + "integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.1.tgz", + "integrity": "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", + "chai": "^6.2.2", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.1.tgz", + "integrity": "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.1", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.1.tgz", + "integrity": "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.1.tgz", + "integrity": "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/utils": "4.1.1", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.1.tgz", + "integrity": "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/pretty-format": "4.1.1", + "@vitest/utils": "4.1.1", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.1.tgz", + "integrity": "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.1.tgz", + "integrity": "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.1", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/miniflare": { + "version": "4.20260317.2", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260317.2.tgz", + "integrity": "sha512-qNL+yWAFMX6fr0pWU6Lx1vNpPobpnDSF1V8eunIckWvoIQl8y1oBjL2RJFEGY3un+l3f9gwW9dirDPP26usYJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "^0.34.5", + "undici": "7.24.4", + "workerd": "1.20260317.1", + "ws": "8.18.0", + "youch": "4.1.0-beta.10" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz", + "integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.6.6.tgz", + "integrity": "sha512-EfqZJytnjJldV+YaaqhthU2oXsEf5e+6rDv957p+zxAvNfFLQOPfvBOTncscQ+akzu6Wrl7s3Pa0LjUQmWJsGQ==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10 || >= 11.0.0-0" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.11.tgz", + "integrity": "sha512-NRjoKMusSjfRbSYiH3VSumlkgFe7kYAa3pzVOsVYVFY3zb5d7nS+a3KGQ7hJKXuYWbzJKPVQ9Wxq2UvyK+ENpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.11" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.11", + "@rolldown/binding-darwin-x64": "1.0.0-rc.11", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.11", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.11", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.11", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.11", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.11", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.11", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.11" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", + "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/undici": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.4.tgz", + "integrity": "sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/vite": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.2.tgz", + "integrity": "sha512-1gFhNi+bHhRE/qKZOJXACm6tX4bA3Isy9KuKF15AgSRuRazNBOJfdDemPBU16/mpMxApDPrWvZ08DcLPEoRnuA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.11", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.1.tgz", + "integrity": "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "4.1.1", + "@vitest/mocker": "4.1.1", + "@vitest/pretty-format": "4.1.1", + "@vitest/runner": "4.1.1", + "@vitest/snapshot": "4.1.1", + "@vitest/spy": "4.1.1", + "@vitest/utils": "4.1.1", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.1", + "@vitest/browser-preview": "4.1.1", + "@vitest/browser-webdriverio": "4.1.1", + "@vitest/ui": "4.1.1", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/workerd": { + "version": "1.20260317.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260317.1.tgz", + "integrity": "sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20260317.1", + "@cloudflare/workerd-darwin-arm64": "1.20260317.1", + "@cloudflare/workerd-linux-64": "1.20260317.1", + "@cloudflare/workerd-linux-arm64": "1.20260317.1", + "@cloudflare/workerd-windows-64": "1.20260317.1" + } + }, + "node_modules/wrangler": { + "version": "4.77.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.77.0.tgz", + "integrity": "sha512-E2Gm69+K++BFd3QvoWjC290RPQj1vDOUotA++sNHmtKPb7EP6C8Qv+1D5Ii73tfZtyNgakpqHlh8lBBbVWTKAQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.2", + "@cloudflare/unenv-preset": "2.16.0", + "blake3-wasm": "2.1.5", + "esbuild": "0.27.3", + "miniflare": "4.20260317.2", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20260317.1" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=20.3.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20260317.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1713b8f --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "dprint-plugins", + "private": true, + "type": "module", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "test": "vitest run" + }, + "dependencies": { + "preact": "^10.29.0", + "preact-render-to-string": "^6.6.6" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "^0.13.4", + "@cloudflare/workers-types": "^4.20260317.1", + "vitest": "~4.1.1", + "wrangler": "^4.77.0" + } +} diff --git a/plugins.test.ts b/plugins.test.ts index 0ac751f..5c4cc01 100644 --- a/plugins.test.ts +++ b/plugins.test.ts @@ -1,29 +1,66 @@ -import { assertEquals } from "@std/assert"; -import { tryResolveLatestJson } from "./plugins.ts"; -import { getLatestReleaseInfo } from "./utils/github.ts"; +import { expect, it } from "vitest"; +import { tryResolveAssetUrl, tryResolveLatestJson } from "./plugins.js"; +import { getLatestReleaseInfo } from "./utils/github.js"; -Deno.test("tryResolveUserLatestJson", async () => { +function resolveAsset(url: string) { + return tryResolveAssetUrl(new URL(url)); +} + +it("tryResolveAssetUrl", () => { + // allowed repo + expect( + resolveAsset( + "https://plugins.dprint.dev/dprint/dprint-plugin-prettier/0.67.0/asset/dprint-plugin-prettier-x86_64-apple-darwin.zip", + ), + ).toEqual( + "https://github.com/dprint/dprint-plugin-prettier/releases/download/0.67.0/dprint-plugin-prettier-x86_64-apple-darwin.zip", + ); + + // latest tag is not allowed + expect( + resolveAsset( + "https://plugins.dprint.dev/dprint/dprint-plugin-prettier/latest/asset/dprint-plugin-prettier-x86_64-apple-darwin.zip", + ), + ).toEqual(undefined); + + // different repo in dprint org also works + expect( + resolveAsset("https://plugins.dprint.dev/dprint/dprint-plugin-exec/0.5.0/asset/some-binary.zip"), + ).toEqual( + "https://github.com/dprint/dprint-plugin-exec/releases/download/0.5.0/some-binary.zip", + ); + + // org not on allow list + expect( + resolveAsset("https://plugins.dprint.dev/someone/some-repo/0.1.0/asset/file.zip"), + ).toEqual(undefined); + + // non-matching URL + expect( + resolveAsset("https://plugins.dprint.dev/dprint/dprint-plugin-prettier/0.67.0/file.zip"), + ).toEqual(undefined); +}); + +it("tryResolveUserLatestJson", async () => { // non-matching - assertEquals( + expect( await tryResolveLatestJson( new URL("https://plugins.dprint.dev/dsherret/latest.json"), ), - undefined, - ); + ).toEqual(undefined); - assertEquals( + expect( await tryResolveLatestJson( new URL("https://plugins.dprint.dev/dsherret/non-existent/latest.json"), ), - 404, - ); + ).toEqual(404); // dprint repo { const result = await getValidResultForUrl("https://plugins.dprint.dev/dprint/typescript/latest.json"); const releaseInfo = await getLatestReleaseInfo("dprint", "dprint-plugin-typescript"); - assertEquals(releaseInfo?.checksum?.length, 64); - assertEquals(result, { + expect(releaseInfo?.checksum?.length).toEqual(64); + expect(result).toEqual({ schemaVersion: 1, url: `https://plugins.dprint.dev/typescript-${releaseInfo!.tagName}.wasm`, version: releaseInfo!.tagName, @@ -36,8 +73,8 @@ Deno.test("tryResolveUserLatestJson", async () => { "https://plugins.dprint.dev/dprint/dprint-plugin-typescript/latest.json", ); const releaseInfo = await getLatestReleaseInfo("dprint", "dprint-plugin-typescript"); - assertEquals(releaseInfo?.checksum?.length, 64); - assertEquals(result, { + expect(releaseInfo?.checksum?.length).toEqual(64); + expect(result).toEqual({ schemaVersion: 1, url: `https://plugins.dprint.dev/typescript-${releaseInfo!.tagName}.wasm`, version: releaseInfo!.tagName, @@ -48,7 +85,7 @@ Deno.test("tryResolveUserLatestJson", async () => { { const result = await getValidResultForUrl("https://plugins.dprint.dev/malobre/vue/latest.json"); const releaseInfo = await getLatestReleaseInfo("malobre", "dprint-plugin-vue"); - assertEquals(result, { + expect(result).toEqual({ schemaVersion: 1, url: `https://plugins.dprint.dev/malobre/vue-${releaseInfo!.tagName}.wasm`, version: releaseInfo!.tagName.replace(/^v/, ""), @@ -59,7 +96,7 @@ Deno.test("tryResolveUserLatestJson", async () => { { const result = await getValidResultForUrl("https://plugins.dprint.dev/malobre/dprint-plugin-vue/latest.json"); const releaseInfo = await getLatestReleaseInfo("malobre", "dprint-plugin-vue"); - assertEquals(result, { + expect(result).toEqual({ schemaVersion: 1, url: `https://plugins.dprint.dev/malobre/vue-${releaseInfo!.tagName}.wasm`, version: releaseInfo!.tagName.replace(/^v/, ""), @@ -70,8 +107,8 @@ Deno.test("tryResolveUserLatestJson", async () => { { const result = await getValidResultForUrl("https://plugins.dprint.dev/dprint/prettier/latest.json"); const releaseInfo = await getLatestReleaseInfo("dprint", "dprint-plugin-prettier"); - assertEquals(releaseInfo?.checksum?.length, 64); - assertEquals(result, { + expect(releaseInfo?.checksum?.length).toEqual(64); + expect(result).toEqual({ schemaVersion: 1, url: `https://plugins.dprint.dev/prettier-${releaseInfo!.tagName}.json`, version: releaseInfo!.tagName, diff --git a/plugins.ts b/plugins.ts index 1e43ca0..ce9f239 100644 --- a/plugins.ts +++ b/plugins.ts @@ -1,4 +1,4 @@ -import { checkGithubRepoExists, getLatestReleaseInfo } from "./utils/mod.ts"; +import { checkGithubRepoExists, getLatestReleaseInfo } from "./utils/mod.js"; const tagPattern = "([A-Za-z0-9\._]+)"; // repos may only contain alphanumeric, underscores, hyphens, and period @@ -22,6 +22,68 @@ const userSchemaPattern = new URLPattern({ pathname: `/${userRepoPattern}/${tagPattern}/schema.json`, }); +// known repos where shortname resolves to dprint-plugin-, +// avoiding a GitHub API call to check existence +const KNOWN_DPRINT_PLUGIN_REPOS = new Set([ + // dprint org + "dprint/dprint-plugin-typescript", + "dprint/dprint-plugin-json", + "dprint/dprint-plugin-markdown", + "dprint/dprint-plugin-toml", + "dprint/dprint-plugin-dockerfile", + "dprint/dprint-plugin-biome", + "dprint/dprint-plugin-oxc", + "dprint/dprint-plugin-mago", + "dprint/dprint-plugin-ruff", + "dprint/dprint-plugin-jupyter", + "dprint/dprint-plugin-prettier", + "dprint/dprint-plugin-roslyn", + "dprint/dprint-plugin-rustfmt", + "dprint/dprint-plugin-yapf", + "dprint/dprint-plugin-exec", + "dprint/dprint-plugin-sql", + // community + "jakebailey/dprint-plugin-gofumpt", + "malobre/dprint-plugin-vue", +]); + +// repos where the short name IS the repo name (no dprint-plugin- prefix) +const KNOWN_NON_PREFIXED_REPOS = new Set([ + "g-plane/malva", + "g-plane/markup_fmt", + "g-plane/pretty_yaml", + "g-plane/pretty_graphql", + "lucacasonato/mf2-tools", +]); + +// orgs/users allowed to serve release assets directly +const ASSET_ALLOWED_ORGS = new Set([ + "dprint", +]); + +const assetNamePattern = "([A-Za-z0-9\\-\\._]+)"; +const assetPattern = new URLPattern({ + pathname: `/${userRepoPattern}/${tagPattern}/asset/${assetNamePattern}`, +}); + +export function tryResolveAssetUrl(url: URL) { + const result = assetPattern.exec(url); + if (!result) { + return undefined; + } + const username = result.pathname.groups[0]!; + const repo = result.pathname.groups[1]!; + if (!ASSET_ALLOWED_ORGS.has(username)) { + return undefined; + } + const tag = result.pathname.groups[2]!; + if (tag === "latest") { + return undefined; + } + const assetName = result.pathname.groups[3]!; + return `https://github.com/${username}/${repo}/releases/download/${tag}/${assetName}`; +} + export async function tryResolvePluginUrl(url: URL) { return dprintPluginTagPatternMapper(dprintWasmPluginPattern, url, "plugin.wasm") ?? dprintPluginTagPatternMapper(dprintProcessPluginPattern, url, "plugin.json") @@ -132,8 +194,10 @@ async function getFullRepoName(username: string, repoName: string) { return repoName; } const fullName = `dprint-plugin-${repoName}`; - // todo: hardcode more repos here - if (username === "jakebailey" && fullName === "dprint-plugin-gofumpt") { + if (KNOWN_NON_PREFIXED_REPOS.has(`${username}/${repoName}`)) { + return repoName; + } + if (KNOWN_DPRINT_PLUGIN_REPOS.has(`${username}/${fullName}`)) { return fullName; } if (await checkGithubRepoExists(username, fullName)) { diff --git a/readInfoFile.ts b/readInfoFile.ts index a60a1c3..41a1164 100644 --- a/readInfoFile.ts +++ b/readInfoFile.ts @@ -1,11 +1,6 @@ -import { getLatestInfo } from "./plugins.ts"; -import { getAllDownloadCount } from "./utils/github.ts"; -import { createAsyncLazy } from "./utils/mod.ts"; - -const infoLazy = createAsyncLazy>(async () => { - const data = await Deno.readTextFile("./info.json"); - return JSON.parse(data); -}); +import infoJson from "./info.json" with { type: "json" }; +import { getLatestInfo } from "./plugins.js"; +import { getAllDownloadCount } from "./utils/github.js"; // only typing what's used on the server export interface PluginsData { @@ -23,13 +18,12 @@ export interface PluginData { } export async function readInfoFile(): Promise> { - const infoObj = await infoLazy.get(); return { - ...infoObj, - latest: await getLatest(infoObj.latest), + ...infoJson, + latest: await getLatest(infoJson.latest), }; - async function getLatest(latest: Readonly[]) { + async function getLatest(latest: typeof infoJson.latest) { const results = []; for (const plugin of latest) { const [username, pluginName] = plugin.name.split("/"); diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..e223f09 --- /dev/null +++ b/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: /$ +Disallow: / diff --git a/style.css b/style.css index ce11505..93bffd3 100644 --- a/style.css +++ b/style.css @@ -14,15 +14,8 @@ body { flex: 1; flex-direction: column; font-family: - "Segoe UI", - Roboto, - Oxygen, - Ubuntu, - Cantarell, - "Fira Sans", - "Droid Sans", - "Helvetica Neue", - sans-serif; + "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", + "Helvetica Neue", sans-serif; } h1 { diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a82a6c6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "types": [ + "@cloudflare/workers-types/2023-07-01", + "@cloudflare/vitest-pool-workers" + ] + }, + "include": ["**/*.ts", "**/*.tsx"] +} diff --git a/utils/LazyExpirableValue.test.ts b/utils/LazyExpirableValue.test.ts index fd04892..1ea601b 100644 --- a/utils/LazyExpirableValue.test.ts +++ b/utils/LazyExpirableValue.test.ts @@ -1,7 +1,7 @@ -import { assertEquals, assertRejects } from "@std/assert"; -import { LazyExpirableValue } from "./LazyExpirableValue.ts"; +import { expect, it } from "vitest"; +import { LazyExpirableValue } from "./LazyExpirableValue.js"; -Deno.test("should work", async () => { +it("should work", async () => { let currentTime = 100; let returnedValue = 1; let deferred = () => @@ -15,21 +15,21 @@ Deno.test("should work", async () => { }, getTime: () => currentTime, }); - assertEquals(await cache.getValue(), 1); + expect(await cache.getValue()).toEqual(1); returnedValue = 2; - assertEquals(await cache.getValue(), 1); + expect(await cache.getValue()).toEqual(1); currentTime = 200; - assertEquals(await cache.getValue(), 1); + expect(await cache.getValue()).toEqual(1); currentTime = 201; - assertEquals(await cache.getValue(), 2); + expect(await cache.getValue()).toEqual(2); currentTime = 302; returnedValue = 3; - assertEquals(await cache.getValue(), 3); + expect(await cache.getValue()).toEqual(3); deferred = () => new Promise((_, reject) => reject(new Error("FAIL"))); - assertEquals(await cache.getValue(), 3); + expect(await cache.getValue()).toEqual(3); currentTime = 403; - await assertRejects(async () => await cache.getValue()); - await assertRejects(async () => await cache.getValue()); + await expect(cache.getValue()).rejects.toThrow(); + await expect(cache.getValue()).rejects.toThrow(); // now try two at the same time let storedResolve: any; @@ -38,9 +38,9 @@ Deno.test("should work", async () => { storedResolve = resolve; }); currentTime = 504; - let p1 = cache.getValue(); - let p2 = cache.getValue(); + const p1 = cache.getValue(); + const p2 = cache.getValue(); storedResolve(10); - assertEquals(await p1, 10); - assertEquals(await p2, 10); + expect(await p1).toEqual(10); + expect(await p2).toEqual(10); }); diff --git a/utils/LruCache.test.ts b/utils/LruCache.test.ts index 05ed42c..cc45e1b 100644 --- a/utils/LruCache.test.ts +++ b/utils/LruCache.test.ts @@ -1,42 +1,42 @@ -import { assertEquals } from "@std/assert"; -import { LruCache, LruCacheWithExpiry } from "./LruCache.ts"; +import { expect, it } from "vitest"; +import { LruCache, LruCacheWithExpiry } from "./LruCache.js"; -Deno.test("LruCache - keeps only most recent", () => { +it("LruCache - keeps only most recent", () => { const cache = new LruCache({ size: 2 }); cache.set(1, 1); cache.set(2, 2); - assertEquals(cache.get(1), 1); - assertEquals(cache.get(2), 2); + expect(cache.get(1)).toEqual(1); + expect(cache.get(2)).toEqual(2); cache.set(3, 3); - assertEquals(cache.get(1), undefined); - assertEquals(cache.get(3), 3); - assertEquals(cache.get(2), 2); // most recent + expect(cache.get(1)).toEqual(undefined); + expect(cache.get(3)).toEqual(3); + expect(cache.get(2)).toEqual(2); // most recent cache.set(4, 4); - assertEquals(cache.get(3), undefined); - assertEquals(cache.get(2), 2); - assertEquals(cache.get(4), 4); + expect(cache.get(3)).toEqual(undefined); + expect(cache.get(2)).toEqual(2); + expect(cache.get(4)).toEqual(4); // adding the same one over and over shouldn't cause anything cache.set(4, 4); cache.set(4, 4); cache.set(4, 4); - assertEquals(cache.get(4), 4); - assertEquals(cache.get(2), 2); + expect(cache.get(4)).toEqual(4); + expect(cache.get(2)).toEqual(2); cache.set(4, 4); cache.set(5, 5); - assertEquals(cache.get(2), undefined); - assertEquals(cache.get(4), 4); - assertEquals(cache.get(5), 5); + expect(cache.get(2)).toEqual(undefined); + expect(cache.get(4)).toEqual(4); + expect(cache.get(5)).toEqual(5); // now ensure removing works cache.remove(5); - assertEquals(cache.get(5), undefined); + expect(cache.get(5)).toEqual(undefined); cache.set(2, 2); - assertEquals(cache.get(4), 4); - assertEquals(cache.get(2), 2); + expect(cache.get(4)).toEqual(4); + expect(cache.get(2)).toEqual(2); }); -Deno.test("LruCacheWithExpiry - expires values after a time", async () => { +it("LruCacheWithExpiry - expires values after a time", async () => { let currentTime = 0; const cache = new LruCacheWithExpiry({ size: 2, @@ -44,25 +44,25 @@ Deno.test("LruCacheWithExpiry - expires values after a time", async () => { getTime: () => currentTime, }); - assertEquals(await cache.getOrSet(1, () => Promise.resolve(1)), 1); - assertEquals(await cache.getOrSet(1, () => Promise.resolve(2)), 1); + expect(await cache.getOrSet(1, () => Promise.resolve(1))).toEqual(1); + expect(await cache.getOrSet(1, () => Promise.resolve(2))).toEqual(1); currentTime = 100; - assertEquals(await cache.getOrSet(1, () => Promise.resolve(2)), 1); + expect(await cache.getOrSet(1, () => Promise.resolve(2))).toEqual(1); currentTime = 101; - assertEquals(await cache.getOrSet(1, () => Promise.resolve(2)), 2); + expect(await cache.getOrSet(1, () => Promise.resolve(2))).toEqual(2); currentTime = 1000; - assertEquals(await cache.getOrSet(1, () => Promise.resolve(1)), 1); + expect(await cache.getOrSet(1, () => Promise.resolve(1))).toEqual(1); currentTime = 1025; - assertEquals(await cache.getOrSet(2, () => Promise.resolve(2)), 2); + expect(await cache.getOrSet(2, () => Promise.resolve(2))).toEqual(2); currentTime = 1050; - assertEquals(await cache.getOrSet(3, () => Promise.resolve(3)), 3); - assertEquals(await cache.getOrSet(1, () => Promise.resolve(11)), 11); - assertEquals(await cache.getOrSet(3, () => Promise.resolve(13)), 3); - assertEquals(await cache.getOrSet(2, () => Promise.resolve(12)), 12); + expect(await cache.getOrSet(3, () => Promise.resolve(3))).toEqual(3); + expect(await cache.getOrSet(1, () => Promise.resolve(11))).toEqual(11); + expect(await cache.getOrSet(3, () => Promise.resolve(13))).toEqual(3); + expect(await cache.getOrSet(2, () => Promise.resolve(12))).toEqual(12); currentTime = 1150; - assertEquals(await cache.getOrSet(3, () => Promise.resolve(13)), 3); + expect(await cache.getOrSet(3, () => Promise.resolve(13))).toEqual(3); currentTime = 1151; - assertEquals(await cache.getOrSet(3, () => Promise.reject(new Error())), 3); - assertEquals(await cache.getOrSet(3, () => Promise.resolve(13)), 13); + expect(await cache.getOrSet(3, () => Promise.reject(new Error()))).toEqual(3); + expect(await cache.getOrSet(3, () => Promise.resolve(13))).toEqual(13); }); diff --git a/utils/RateLimiter.test.ts b/utils/RateLimiter.test.ts index ee97813..4bbdc8d 100644 --- a/utils/RateLimiter.test.ts +++ b/utils/RateLimiter.test.ts @@ -1,8 +1,8 @@ -import { assert } from "@std/assert"; -import { Clock } from "./clock.ts"; -import { RateLimiter } from "./RateLimiter.ts"; +import { expect, it } from "vitest"; +import { Clock } from "./clock.js"; +import { RateLimiter } from "./RateLimiter.js"; -Deno.test("rate limits", () => { +it("rate limits", () => { let time = 0; const clock: Clock = { getTime() { @@ -14,17 +14,17 @@ Deno.test("rate limits", () => { timeWindowMs: 1000, }); - assert(rateLimiter.isAllowed("127.0.0.1")); + expect(rateLimiter.isAllowed("127.0.0.1")).toBeTruthy(); time += 100; - assert(rateLimiter.isAllowed("127.0.0.1")); - assert(!rateLimiter.isAllowed("127.0.0.1")); + expect(rateLimiter.isAllowed("127.0.0.1")).toBeTruthy(); + expect(rateLimiter.isAllowed("127.0.0.1")).toBeFalsy(); time += 500; - assert(!rateLimiter.isAllowed("127.0.0.1")); + expect(rateLimiter.isAllowed("127.0.0.1")).toBeFalsy(); time += 500; - assert(rateLimiter.isAllowed("127.0.0.1")); - assert(!rateLimiter.isAllowed("127.0.0.1")); + expect(rateLimiter.isAllowed("127.0.0.1")).toBeTruthy(); + expect(rateLimiter.isAllowed("127.0.0.1")).toBeFalsy(); time += 500; - assert(rateLimiter.isAllowed("127.0.0.1")); - assert(!rateLimiter.isAllowed("127.0.0.1")); - assert(rateLimiter.isAllowed("127.0.0.2")); + expect(rateLimiter.isAllowed("127.0.0.1")).toBeTruthy(); + expect(rateLimiter.isAllowed("127.0.0.1")).toBeFalsy(); + expect(rateLimiter.isAllowed("127.0.0.2")).toBeTruthy(); }); diff --git a/utils/RateLimiter.ts b/utils/RateLimiter.ts index 38a8e29..64e16ea 100644 --- a/utils/RateLimiter.ts +++ b/utils/RateLimiter.ts @@ -1,5 +1,5 @@ -import { Clock } from "./clock.ts"; -import { LruCache } from "./LruCache.ts"; +import { Clock } from "./clock.js"; +import { LruCache } from "./LruCache.js"; export interface RateLimiterOptions { limit: number; diff --git a/utils/asyncLazy.test.ts b/utils/asyncLazy.test.ts index 10c3708..4bac170 100644 --- a/utils/asyncLazy.test.ts +++ b/utils/asyncLazy.test.ts @@ -1,18 +1,18 @@ -import { assertEquals, assertRejects } from "@std/assert"; -import { createAsyncLazy } from "./asyncLazy.ts"; +import { expect, it } from "vitest"; +import { createAsyncLazy } from "./asyncLazy.js"; -Deno.test("should create", async () => { +it("should create", async () => { let createdValue = () => Promise.resolve(1); const asyncLazy = createAsyncLazy(() => createdValue()); - assertEquals(await asyncLazy.get(), 1); + expect(await asyncLazy.get()).toEqual(1); createdValue = () => Promise.resolve(2); - assertEquals(await asyncLazy.get(), 1); + expect(await asyncLazy.get()).toEqual(1); }); -Deno.test("should create when throws first time", async () => { +it("should create when throws first time", async () => { let createdValue: () => Promise = () => Promise.reject(new Error()); const asyncLazy = createAsyncLazy(() => createdValue()); - assertRejects(() => asyncLazy.get()); + await expect(asyncLazy.get()).rejects.toThrow(); createdValue = () => Promise.resolve(1); - assertEquals(await asyncLazy.get(), 1); + expect(await asyncLazy.get()).toEqual(1); }); diff --git a/utils/fetchCached.test.ts b/utils/fetchCached.test.ts deleted file mode 100644 index de22727..0000000 --- a/utils/fetchCached.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { assertEquals } from "@std/assert"; -import { createFetchCacher } from "./fetchCached.ts"; - -Deno.test("should error when going above 10mb", { - // not sure why this stopped working on the CI - ignore: !!Deno.env.get("CI"), -}, async (t) => { - let time = 0; - const clock = { - getTime() { - return time; - }, - }; - const { fetchCached } = createFetchCacher(clock); - await using server = Deno.serve((request) => { - if (request.url.endsWith("large")) { - return new Response(new Uint8Array(11 * 1024 * 1024).buffer, { - status: 200, - }); - } else if (request.url.endsWith("small")) { - return new Response(new Uint8Array(9 * 1024 * 1024).buffer, { - status: 200, - }); - } else { - return new Response("Not found", { - status: 404, - }); - } - }); - - const addr = server.addr.hostname + ":" + server.addr.port; - - // large - await t.step("should error going above 10mb", async () => { - const response = await fetchCached({ - url: `http://${addr}/large`, - hostname: server.addr.hostname, - }); - if (response.kind !== "error") { - throw new Error("Expected error."); - } - assertEquals(response.response.status, 413); - }); - - // small - await t.step("should not error below 10mb", async () => { - const response = await fetchCached({ - url: `http://${addr}/small`, - hostname: server.addr.hostname, - }); - if (response.kind !== "success") { - throw new Error("Expected error."); - } - assertEquals(response.body.byteLength, 9 * 1024 * 1024); - - const response2 = await fetchCached({ - url: `http://${addr}/small`, - hostname: server.addr.hostname, - }); - if (response.body !== response2.body) { - throw new Error("Should have been the same objects."); - } - }); - - await t.step("should error after 20 downloads because of rate limiting", async () => { - for (let i = 0; i < 19; i++) { - const response = await fetchCached({ - url: `http://${addr}/small`, - hostname: server.addr.hostname, - }); - assertEquals(response.kind, "success"); - } - - let response = await fetchCached({ - url: `http://${addr}/small`, - hostname: server.addr.hostname, - }); - if (response.kind !== "error") { - throw new Error("Was not error."); - } - assertEquals(response.response.status, 429); - // advance time and it should work again - time += 61 * 1000; - response = await fetchCached({ - url: `http://${addr}/small`, - hostname: server.addr.hostname, - }); - assertEquals(response.kind, "success"); - }); -}); diff --git a/utils/fetchCached.ts b/utils/fetchCached.ts deleted file mode 100644 index d4769d6..0000000 --- a/utils/fetchCached.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Clock } from "./clock.ts"; -import { LruCache, LruCacheSet } from "./LruCache.ts"; -import { RateLimiter } from "./RateLimiter.ts"; - -const tooLargeResponse = { - kind: "error" as const, - response: new Response("Response body exceeds 10MB limit", { - status: 413, - }), -}; -const tooManyRequestsResponse = { - kind: "error" as const, - response: new Response("Too many requests", { - status: 429, - }), -}; - -export function createFetchCacher(clock: Clock) { - const directDownloadRateLimiter = new RateLimiter(clock, { - limit: 10, - timeWindowMs: 5 * 60 * 1_000, - }); - const cachedRateLimiter = new RateLimiter(clock, { - limit: 20, - timeWindowMs: 60 * 1_000, - }); - const cache = new LruCache({ size: 50 }); - const tooLargeCache = new LruCacheSet({ size: 1000 }); - - return { - async fetchCached({ url, hostname }: { url: string; hostname: string }) { - let cachedBody = cache.get(url); - if (cachedBody == null) { - if (!directDownloadRateLimiter.isAllowed(hostname)) { - return tooManyRequestsResponse; - } - - const response = await fetch(url); - if (!response.ok) { - return { - kind: "error", - response, - } as const; - } - - const reader = response.body!.getReader(); - - let receivedLength = 0; // received that many bytes at the moment - let chunks = []; // array of received binary chunks (comprises the body) - while (true) { - const { done, value } = await reader.read(); - - if (done) { - break; - } - - chunks.push(value); - receivedLength += value.length; - - // Check if the received length is greater than 10MB - if (receivedLength > 10 * 1024 * 1024) { - reader.cancel(); // stops the reading process - tooLargeCache.insert(url); - return tooLargeResponse; - } - } - - // Concatenate chunks into single Uint8Array - let chunksAll = new Uint8Array(receivedLength); - let position = 0; - for (let chunk of chunks) { - chunksAll.set(chunk, position); - position += chunk.length; - } - - cachedBody = chunksAll.buffer; - cache.set(url, cachedBody); - } else if (!cachedRateLimiter.isAllowed(hostname)) { - return tooManyRequestsResponse; - } - return { - kind: "success", - body: cachedBody, - } as const; - }, - }; -} diff --git a/utils/github.test.ts b/utils/github.test.ts index 9ab6019..a744a28 100644 --- a/utils/github.test.ts +++ b/utils/github.test.ts @@ -1,14 +1,14 @@ -import { assertEquals } from "@std/assert"; -import { checkGithubRepoExists } from "./github.ts"; +import { expect, it } from "vitest"; +import { checkGithubRepoExists } from "./github.js"; -Deno.test("should get when exists", async () => { +it("should get when exists", async () => { const getResult = () => checkGithubRepoExists("dprint", "dprint"); - assertEquals(await getResult(), true); - assertEquals(await getResult(), true); + expect(await getResult()).toEqual(true); + expect(await getResult()).toEqual(true); }); -Deno.test("should get when not exists", async () => { +it("should get when not exists", async () => { const getResult = () => checkGithubRepoExists("dsherret", "some-random-name"); - assertEquals(await getResult(), false); - assertEquals(await getResult(), false); + expect(await getResult()).toEqual(false); + expect(await getResult()).toEqual(false); }); diff --git a/utils/github.ts b/utils/github.ts index 752719b..3ed753f 100644 --- a/utils/github.ts +++ b/utils/github.ts @@ -1,6 +1,7 @@ -import { LazyExpirableValue } from "./LazyExpirableValue.ts"; -import { LruCache, LruCacheWithExpiry } from "./LruCache.ts"; -import { createSynchronizedActioner } from "./synchronizedActioner.ts"; +import { env } from "cloudflare:workers"; +import { LazyExpirableValue } from "./LazyExpirableValue.js"; +import { LruCache, LruCacheWithExpiry } from "./LruCache.js"; +import { createSynchronizedActioner } from "./synchronizedActioner.js"; const repoExistsCache = new LruCache({ size: 1000 }); @@ -38,11 +39,6 @@ export interface ReleaseInfo { downloadCount: number; } -const latestReleaseTagNameCache = new LruCacheWithExpiry({ - size: 1000, - expiryMs: 5 * 60 * 1_000, // keep entries for 5 minutes -}); - export async function getLatestReleaseInfo(username: string, repoName: string) { const releases = await getReleasesData(username, repoName); const latest = releases?.find((release) => !release.draft && !release.prerelease); @@ -118,7 +114,7 @@ interface GitHubRelease { assets: ReleaseAsset[]; } -const releasesCache = new LruCacheWithExpiry({ +const releasesCache = new LruCacheWithExpiry({ size: 1000, expiryMs: 5 * 60 * 1_000, // keep entries for 5 minutes }); @@ -132,7 +128,7 @@ async function getReleasesData(username: string, repoName: string) { return await releasesCache.getOrSet(url, async () => { const response = await makeGitHubGetRequest(url, "GET"); if (response.status === 404) { - await response.text(); // todo: no way to mark this as used for the sanitizers? + await response.text(); return; } else if (!response.ok) { const text = await response.text(); @@ -142,7 +138,7 @@ async function getReleasesData(username: string, repoName: string) { }); } -const latestCliReleaseInfo = new LazyExpirableValue({ +const latestCliReleaseInfo = new LazyExpirableValue<{ tag_name: string }>({ expiryMs: 10 * 60 * 1_000, // keep for 10 minutes createValue: async () => { const url = `https://api.github.com/repos/dprint/dprint/releases/latest`; @@ -185,8 +181,9 @@ function makeGitHubGetRequest(url: string, method: "GET" | "HEAD") { function getGitHubHeaders() { const headers: Record = { "accept": "application/vnd.github.v3+json", + "user-agent": "dprint-plugins", }; - const token = Deno.env.get("DPRINT_PLUGINS_GH_TOKEN"); + const token = env.DPRINT_PLUGINS_GH_TOKEN; if (token != null) { headers["authorization"] = "token " + token; } diff --git a/utils/mod.ts b/utils/mod.ts index 408bd9f..e48b202 100644 --- a/utils/mod.ts +++ b/utils/mod.ts @@ -1,4 +1,3 @@ -export * from "./asyncLazy.ts"; -export * from "./fetchCached.ts"; -export * from "./github.ts"; -export * from "./version.ts"; +export * from "./asyncLazy.js"; +export * from "./github.js"; +export * from "./version.js"; diff --git a/utils/r2Cache.ts b/utils/r2Cache.ts new file mode 100644 index 0000000..66e019c --- /dev/null +++ b/utils/r2Cache.ts @@ -0,0 +1,39 @@ +import { env } from "cloudflare:workers"; + +const GITHUB_PREFIX = "https://github.com/"; + +function toR2Key(githubUrl: string) { + if (githubUrl.startsWith(GITHUB_PREFIX)) { + return githubUrl.slice(GITHUB_PREFIX.length); + } + return githubUrl; +} + +function isLatestUrl(githubUrl: string) { + return githubUrl.includes("/releases/latest/"); +} + +export async function r2Get(githubUrl: string): Promise { + if (isLatestUrl(githubUrl)) { + return null; + } + try { + return await env.PLUGIN_CACHE.get(toR2Key(githubUrl)); + } catch (err) { + console.error("R2 get error:", err); + return null; + } +} + +export async function r2Put(githubUrl: string, body: ArrayBuffer, contentType: string): Promise { + if (isLatestUrl(githubUrl)) { + return; + } + try { + await env.PLUGIN_CACHE.put(toR2Key(githubUrl), body, { + httpMetadata: { contentType }, + }); + } catch (err) { + console.error("R2 put error:", err); + } +} diff --git a/utils/synchronizedActioner.ts b/utils/synchronizedActioner.ts index 82a7bc3..a0982de 100644 --- a/utils/synchronizedActioner.ts +++ b/utils/synchronizedActioner.ts @@ -1,4 +1,4 @@ -import { withTimeout } from "./withTimeout.ts"; +import { withTimeout } from "./withTimeout.js"; /** Creates an object that only lets one action execute at a time. */ export function createSynchronizedActioner() { diff --git a/utils/version.test.ts b/utils/version.test.ts index 0afe366..ae8f093 100644 --- a/utils/version.test.ts +++ b/utils/version.test.ts @@ -1,15 +1,15 @@ -import { assertEquals } from "@std/assert"; -import { parseVersion, Version } from "./version.ts"; +import { expect, it } from "vitest"; +import { parseVersion, Version } from "./version.js"; -Deno.test("should parse version", () => { - assertEquals(parseVersion("12.34.56"), new Version(12, 34, 56)); +it("should parse version", () => { + expect(parseVersion("12.34.56")).toEqual(new Version(12, 34, 56)); }); function runVersionLessThanTest(a: string, b: string, expected: boolean) { - assertEquals(parseVersion(a).lessThan(parseVersion(b)), expected); + expect(parseVersion(a).lessThan(parseVersion(b))).toEqual(expected); } -Deno.test("less than tests", () => { +it("less than tests", () => { runVersionLessThanTest("1.3.5", "1.3.5", false); runVersionLessThanTest("1.3.4", "1.3.5", true); @@ -22,10 +22,10 @@ Deno.test("less than tests", () => { }); function runEqualTest(a: string, b: string, expected: boolean) { - assertEquals(parseVersion(a).equal(parseVersion(b)), expected); + expect(parseVersion(a).equal(parseVersion(b))).toEqual(expected); } -Deno.test("equal tests", () => { +it("equal tests", () => { runEqualTest("1.3.5", "1.3.5", true); runEqualTest("1.3.6", "1.3.5", false); @@ -34,10 +34,10 @@ Deno.test("equal tests", () => { }); function runLessThanEqual(a: string, b: string, expected: boolean) { - assertEquals(parseVersion(a).lessThanEqual(parseVersion(b)), expected); + expect(parseVersion(a).lessThanEqual(parseVersion(b))).toEqual(expected); } -Deno.test("less than equal tests", () => { +it("less than equal tests", () => { runLessThanEqual("1.3.4", "1.3.5", true); runLessThanEqual("1.3.5", "1.3.5", true); runLessThanEqual("1.3.6", "1.3.5", false); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..b6c748a --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,10 @@ +import { cloudflareTest } from "@cloudflare/vitest-pool-workers"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: "./wrangler.toml" }, + }), + ], +}); diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..d997d95 --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,12 @@ +name = "dprint-plugins" +main = "main.ts" +compatibility_date = "2024-02-24" +compatibility_flags = ["nodejs_compat"] + +rules = [ + { type = "Text", globs = ["**/*.css", "**/*.txt"], fallthrough = true }, +] + +[[r2_buckets]] +binding = "PLUGIN_CACHE" +bucket_name = "dprint-plugin-cache"