diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8399346f3..6642b7233 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,6 +32,9 @@ jobs: - name: Build SolidStart run: pnpm --filter @solidjs/start build + - name: Build @solidjs/image + run: pnpm --filter @solidjs/image build + - name: Build Test Project run: pnpm --filter tests build diff --git a/.gitignore b/.gitignore index ba104be4b..a6414f0c3 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ tsconfig.tsbuildinfo apps/tests/**/test-results .solid-start .pnpm-store + +.image diff --git a/apps/tests/package.json b/apps/tests/package.json index b264f827f..8348853b5 100644 --- a/apps/tests/package.json +++ b/apps/tests/package.json @@ -15,6 +15,7 @@ "test:all": "pnpm run unit:ci && pnpm run e2e && pnpm run e2e:bundled-dev" }, "dependencies": { + "@solidjs/image": "workspace:*", "@solidjs/meta": "^0.29.4", "@solidjs/router": "^0.15.4", "@solidjs/start": "workspace:*", diff --git a/apps/tests/src/images/example.jpg b/apps/tests/src/images/example.jpg new file mode 100644 index 000000000..229e7980c Binary files /dev/null and b/apps/tests/src/images/example.jpg differ diff --git a/apps/tests/src/routes/image-local.tsx b/apps/tests/src/routes/image-local.tsx new file mode 100644 index 000000000..52a621316 --- /dev/null +++ b/apps/tests/src/routes/image-local.tsx @@ -0,0 +1,31 @@ +import { SolidImage as Image } from "@solidjs/image"; +import { type JSX, onMount, Show } from "solid-js"; +import exampleImage from "../images/example.jpg?image"; + +interface PlaceholderProps { + show: () => void; +} + +function Placeholder(props: PlaceholderProps): JSX.Element { + onMount(() => { + props.show(); + }); + + return
Loading...
; +} + +export default function App(): JSX.Element { + return ( +
+ example ( + + + + )} + /> +
+ ); +} diff --git a/apps/tests/src/routes/image-remote.tsx b/apps/tests/src/routes/image-remote.tsx new file mode 100644 index 000000000..54d87b488 --- /dev/null +++ b/apps/tests/src/routes/image-remote.tsx @@ -0,0 +1,35 @@ +import { SolidImage as Image } from "@solidjs/image"; +import { type JSX, onMount, Show } from "solid-js"; +// local +// import exampleImage from './example.jpg?image'; + +// remote +import exampleImage from "image:foobar"; + +interface PlaceholderProps { + show: () => void; +} + +function Placeholder(props: PlaceholderProps): JSX.Element { + onMount(() => { + props.show(); + }); + + return
Loading...
; +} + +export default function App(): JSX.Element { + return ( +
+ example ( + + + + )} + /> +
+ ); +} diff --git a/apps/tests/tsconfig.json b/apps/tests/tsconfig.json index 376a974e4..67b634e1a 100644 --- a/apps/tests/tsconfig.json +++ b/apps/tests/tsconfig.json @@ -10,7 +10,7 @@ "allowJs": true, "strict": true, "noEmit": true, - "types": ["vitest/globals", "@testing-library/jest-dom", "@solidjs/start/env"], + "types": ["vitest/globals", "@testing-library/jest-dom", "@solidjs/start/env", "@solidjs/image/env"], "isolatedModules": true, "paths": { "~/*": ["./src/*"], diff --git a/apps/tests/vite.config.ts b/apps/tests/vite.config.ts index 7ac29edec..aed9dc3d1 100644 --- a/apps/tests/vite.config.ts +++ b/apps/tests/vite.config.ts @@ -1,6 +1,7 @@ import { nitro } from "nitro/vite"; import { defineConfig } from "vite"; import { solidStart } from "../../packages/start/src/config"; +import { imagePlugin } from '../../packages/image/src/vite'; export default defineConfig({ server: { @@ -28,5 +29,45 @@ export default defineConfig({ nitro({ compressPublicAssets: true, }), + imagePlugin({ + local: { + sizes: [480, 600], + quality: 80, + publicPath: "public", + }, + remote: { + transformURL(url) { + return { + src: { + source: `https://picsum.photos/seed/${url}/1200/900.webp`, + width: 1080, + height: 760, + }, + variants: [ + { + path: `https://picsum.photos/seed/${url}/800/600.jpg`, + width: 800, + type: "image/jpeg", + }, + { + path: `https://picsum.photos/seed/${url}/400/300.jpg`, + width: 400, + type: "image/jpeg", + }, + { + path: `https://picsum.photos/seed/${url}/800/600.png`, + width: 800, + type: "image/png", + }, + { + path: `https://picsum.photos/seed/${url}/400/300.png`, + width: 400, + type: "image/png", + }, + ], + }; + }, + }, + }), ], }); diff --git a/packages/image/README.md b/packages/image/README.md new file mode 100644 index 000000000..600397995 --- /dev/null +++ b/packages/image/README.md @@ -0,0 +1,185 @@ +# `@solidjs/image` + +## Install + +```bash +npm i @solidjs/image +``` + +```bash +pnpm add @solidjs/image +``` + +## Setup + +### Vite + +```ts +import { imagePlugin } from '@solidjs/image/vite'; + +export default defineConfig({ + plugins: { + imagePlugin({ + /** + * Used to process local image imports + * + * example: + * import myImage from './path/to/my-image.jpg?image'; + */ + local: { + /** + * Image formats that can be processed + */ + input: ['jpeg', 'png'], + /** + * Image format for the output images. + * + * Take note that each input image will + * produce a new image for each format + */ + output: ['jpeg', 'png'], + /** + * Sizes of the output images, based on width + * while retaining the aspect ratio. + * + * This option also produces an image for + * each width and for each output format. + */ + sizes: [480, 600], + /** + * Quality of the processed images + */ + quality: 80, + /** + * Where the processed images as emitted + */ + publicPath: "public", + }, + /** + * Used for remote images + * + * example: + * import myImage from 'image:my-value'; + */ + remote: { + /** + * Transforms the right-hand part of the `image:*` string + */ + transformURL(url) { + return { + /** + * The default image for the given url + */ + src: { + source: `https://picsum.photos/seed/${url}/1200/900.webp`, + width: 1080, + height: 760, + }, + /** + * Variants of the image (format, size) for responsiveness + */ + variants: [ + { + path: `https://picsum.photos/seed/${url}/800/600.jpg`, + width: 800, + type: "image/jpeg", + }, + { + path: `https://picsum.photos/seed/${url}/400/300.jpg`, + width: 400, + type: "image/jpeg", + }, + { + path: `https://picsum.photos/seed/${url}/800/600.png`, + width: 800, + type: "image/png", + }, + { + path: `https://picsum.photos/seed/${url}/400/300.png`, + width: 400, + type: "image/png", + }, + ], + }; + }, + }, + }), + } +}); +``` + +## Usage + +### Local image + +```tsx +import { SolidImage as Image } from "@solidjs/image"; +import { type JSX, onMount, Show } from "solid-js"; + +import exampleImage from "../images/example.jpg?image"; + +interface PlaceholderProps { + show: () => void; +} + +function Placeholder(props: PlaceholderProps): JSX.Element { + onMount(() => { + props.show(); + }); + + return
Loading...
; +} + +export default function App(): JSX.Element { + return ( +
+ example ( + + + + )} + /> +
+ ); +} +``` + +### Remote image + +```tsx +import { SolidImage as Image } from "@solidjs/image"; +import { type JSX, onMount, Show } from "solid-js"; + +import exampleImage from "image:foobar"; + +interface PlaceholderProps { + show: () => void; +} + +function Placeholder(props: PlaceholderProps): JSX.Element { + onMount(() => { + props.show(); + }); + + return
Loading...
; +} + +export default function App(): JSX.Element { + return ( +
+ example ( + + + + )} + /> +
+ ); +} +``` diff --git a/packages/image/package.json b/packages/image/package.json new file mode 100644 index 000000000..779c4d20c --- /dev/null +++ b/packages/image/package.json @@ -0,0 +1,44 @@ +{ + "name": "@solidjs/image", + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/solidjs/solid-start.git", + "directory": "packages/image" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist" + ], + "scripts": { + "prepublishOnly": "tsdown", + "build": "tsdown", + "watch": "tsdown --watch", + "test": "vitest" + }, + "devDependencies": { + "@tsdown/css": "^0.22.12", + "@types/node": "^25.5.0", + "solid-js": "^1.9.9", + "tsdown": "^0.22.12", + "vite": "^8.1.5", + "vite-plugin-solid": "^2.11.11", + "vitest": "^4.0.10" + }, + "dependencies": { + "sharp": "^0.35.3" + }, + "peerDependencies": { + "solid-js": "^1.9.9", + "vite": "^8 || ^9" + }, + "exports": { + ".": "./dist/index.jsx", + "./env": "./dist/env.js", + "./vite": "./dist/vite.js", + "./package.json": "./package.json", + "./style.css": "./dist/style.css" + } +} diff --git a/packages/image/src/__tests__/components.test.tsx b/packages/image/src/__tests__/components.test.tsx new file mode 100644 index 000000000..357191315 --- /dev/null +++ b/packages/image/src/__tests__/components.test.tsx @@ -0,0 +1,116 @@ +import { createRoot } from "solid-js"; +import { renderToString } from "solid-js/web"; +import { describe, expect, it } from "vitest"; +import { ClientOnly, createClientSignal } from "../core/client-only"; +import { createLazyRender } from "../core/create-lazy-render"; +import { SolidImage } from "../core/index"; + +// --------------------------------------------------------------------------- +// createLazyRender +// --------------------------------------------------------------------------- +describe("createLazyRender", () => { + it("starts with visible = false", () => { + let visible: boolean | undefined; + + createRoot(dispose => { + const laze = createLazyRender(); + visible = laze.visible; + dispose(); + }); + + expect(visible).toBe(false); + }); + + it("exposes a callable ref setter", () => { + createRoot(dispose => { + const laze = createLazyRender(); + expect(typeof laze.ref).toBe("function"); + dispose(); + }); + }); + + it("returns correct shape with refresh option", () => { + createRoot(dispose => { + const laze = createLazyRender({ refresh: true }); + expect(typeof laze.ref).toBe("function"); + expect(laze.visible).toBe(false); + dispose(); + }); + }); +}); + +// --------------------------------------------------------------------------- +// createClientSignal (server context -- isServer is true in Node) +// --------------------------------------------------------------------------- +describe("createClientSignal", () => { + it("returns a function that resolves to false on the server", () => { + const signal = createClientSignal(); + expect(typeof signal).toBe("function"); + expect(signal()).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// ClientOnly (server context) +// --------------------------------------------------------------------------- +describe("ClientOnly", () => { + it("renders the fallback in a server environment", () => { + const html = renderToString(() => ( + loading}> + client content + + )); + + expect(html).toContain("loading"); + expect(html).not.toContain("client content"); + }); + + it("renders the fallback element when no children are given", () => { + const html = renderToString(() => placeholder} />); + + expect(html).toContain("placeholder"); + }); +}); + +// --------------------------------------------------------------------------- +// SolidImage SSR regression +// --------------------------------------------------------------------------- +describe("SolidImage SSR", () => { + it("does not throw ReferenceError: document is not defined", () => { + expect(() => { + renderToString(() => ( +
loading
} + /> + )); + }).not.toThrow(); + }); + + it("produces HTML containing the image container", () => { + const html = renderToString(() => ( +
loading
} + /> + )); + + expect(html).toContain("data-start-image"); + expect(html).toContain("test.jpg"); + }); + + it("renders without a transformer (default fallback path)", () => { + const html = renderToString(() => ( + placeholder} + /> + )); + + expect(html).toContain("hero.png"); + expect(html).toContain('alt="hero image"'); + }); +}); diff --git a/packages/image/src/__tests__/transformer.test.ts b/packages/image/src/__tests__/transformer.test.ts new file mode 100644 index 000000000..9c7ca99c7 --- /dev/null +++ b/packages/image/src/__tests__/transformer.test.ts @@ -0,0 +1,174 @@ +import { describe, expect, it } from "vitest"; +import { + createImageVariants, + mergeImageVariantsByType, + mergeImageVariantsToSrcSet, +} from "../core/transformer"; +import type { SolidImageSource, SolidImageTransformer, SolidImageVariant } from "../core/types"; + +describe("createImageVariants", () => { + it("returns an array with 1 item when transformer returns a single variant", () => { + const source: SolidImageSource<{}> = { + source: "/img/photo.jpg", + width: 800, + height: 600, + options: {}, + }; + + const transformer: SolidImageTransformer<{}> = { + transform: () => ({ + path: "/img/photo-800.webp", + width: 800, + type: "image/webp", + }), + }; + + const result = createImageVariants(source, transformer); + + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + path: "/img/photo-800.webp", + width: 800, + type: "image/webp", + }); + }); + + it("returns an array with 3 items when transformer returns 3 variants", () => { + const source: SolidImageSource<{}> = { + source: "/img/hero.png", + width: 1200, + height: 800, + options: {}, + }; + + const variants: SolidImageVariant[] = [ + { path: "/img/hero-400.avif", width: 400, type: "image/avif" }, + { path: "/img/hero-800.avif", width: 800, type: "image/avif" }, + { path: "/img/hero-1200.avif", width: 1200, type: "image/avif" }, + ]; + + const transformer: SolidImageTransformer<{}> = { + transform: () => variants, + }; + + const result = createImageVariants(source, transformer); + + expect(result).toHaveLength(3); + expect(result[0]).toEqual({ + path: "/img/hero-400.avif", + width: 400, + type: "image/avif", + }); + expect(result[1]).toEqual({ + path: "/img/hero-800.avif", + width: 800, + type: "image/avif", + }); + expect(result[2]).toEqual({ + path: "/img/hero-1200.avif", + width: 1200, + type: "image/avif", + }); + }); + + it("passes the source to the transformer", () => { + const source: SolidImageSource<{ quality: number }> = { + source: "/img/test.jpg", + width: 500, + height: 300, + options: { quality: 80 }, + }; + + let receivedSource: SolidImageSource<{ quality: number }> | undefined; + + const transformer: SolidImageTransformer<{ quality: number }> = { + transform: src => { + receivedSource = src; + return { path: "/img/test-500.webp", width: 500, type: "image/webp" }; + }, + }; + + createImageVariants(source, transformer); + + expect(receivedSource).toBe(source); + }); +}); + +describe("mergeImageVariantsByType", () => { + it("groups variants by their MIME type", () => { + const variants: SolidImageVariant[] = [ + { path: "/img/a-400.webp", width: 400, type: "image/webp" }, + { path: "/img/a-800.webp", width: 800, type: "image/webp" }, + { path: "/img/a-400.avif", width: 400, type: "image/avif" }, + { path: "/img/a-800.avif", width: 800, type: "image/avif" }, + ]; + + const result = mergeImageVariantsByType(variants); + + expect(result.size).toBe(2); + + const webpGroup = result.get("image/webp")!; + expect(webpGroup).toHaveLength(2); + expect(webpGroup[0]).toEqual({ + path: "/img/a-400.webp", + width: 400, + type: "image/webp", + }); + expect(webpGroup[1]).toEqual({ + path: "/img/a-800.webp", + width: 800, + type: "image/webp", + }); + + const avifGroup = result.get("image/avif")!; + expect(avifGroup).toHaveLength(2); + expect(avifGroup[0]).toEqual({ + path: "/img/a-400.avif", + width: 400, + type: "image/avif", + }); + expect(avifGroup[1]).toEqual({ + path: "/img/a-800.avif", + width: 800, + type: "image/avif", + }); + }); + + it("returns a Map with 1 entry when all variants share the same type", () => { + const variants: SolidImageVariant[] = [ + { path: "/img/x-100.png", width: 100, type: "image/png" }, + { path: "/img/x-200.png", width: 200, type: "image/png" }, + ]; + + const result = mergeImageVariantsByType(variants); + + expect(result.size).toBe(1); + expect(result.get("image/png")).toHaveLength(2); + }); +}); + +describe("mergeImageVariantsToSrcSet", () => { + it("formats a single variant into a srcset string", () => { + const variants: SolidImageVariant[] = [ + { path: "/img/photo-100.webp", width: 100, type: "image/webp" }, + ]; + + const result = mergeImageVariantsToSrcSet(variants); + + expect(result).toBe("/img/photo-100.webp 100w"); + }); + + it("formats multiple variants into a comma-separated srcset string", () => { + const variants: SolidImageVariant[] = [ + { path: "/img/photo-100.webp", width: 100, type: "image/webp" }, + { path: "/img/photo-200.webp", width: 200, type: "image/webp" }, + { path: "/img/photo-400.webp", width: 400, type: "image/webp" }, + ]; + + const result = mergeImageVariantsToSrcSet(variants); + + expect(result).toBe( + "/img/photo-100.webp 100w,/img/photo-200.webp 200w,/img/photo-400.webp 400w", + ); + }); +}); diff --git a/packages/image/src/core/aspect-ratio.ts b/packages/image/src/core/aspect-ratio.ts new file mode 100644 index 000000000..962464188 --- /dev/null +++ b/packages/image/src/core/aspect-ratio.ts @@ -0,0 +1,86 @@ +function gcd(a: number, b: number): number { + if (b === 0) { + return a; + } + return gcd(b, a % b); +} + +export interface AspectRatio { + width: number; + height: number; +} + +const HORIZONTAL_ASPECT_RATIO = [ + { width: 4, height: 4 }, // Square + { width: 4, height: 3 }, // Standard Fullscreen + { width: 16, height: 10 }, // Standard LCD + { width: 16, height: 9 }, // HD + // { width: 37, height: 20 }, // Widescreen + { width: 6, height: 3 }, // Univisium + { width: 21, height: 9 }, // Anamorphic 2.35:1 + // { width: 64, height: 27 }, // Anamorphic 2.39:1 or 2.37:1 + { width: 19, height: 16 }, // Movietone + { width: 5, height: 4 }, // 17' LCD CRT + // { width: 48, height: 35 }, // 16mm and 35mm + { width: 11, height: 8 }, // 35mm full sound + // { width: 143, height: 100 }, // IMAX + { width: 6, height: 4 }, // 35mm photo + { width: 14, height: 9 }, // commercials + { width: 5, height: 3 }, // Paramount + { width: 7, height: 4 }, // early 35mm + { width: 11, height: 5 }, // 70mm + { width: 12, height: 5 }, // Bluray + { width: 8, height: 3 }, // Super 16 + { width: 18, height: 5 }, // IMAX + { width: 12, height: 3 }, // Polyvision +]; + +const VERTICAL_ASPECT_RATIO = HORIZONTAL_ASPECT_RATIO.map(item => ({ + width: item.height, + height: item.width, +})); + +const ASPECT_RATIO = [...HORIZONTAL_ASPECT_RATIO, ...VERTICAL_ASPECT_RATIO]; + +export function getAspectRatio({ width, height }: AspectRatio): AspectRatio { + const denom = gcd(width, height); + + return { + width: width / denom, + height: height / denom, + }; +} + +export function getNearestAspectRatio(ratio: AspectRatio): AspectRatio { + let nearest = Number.MAX_VALUE; + let id = 0; + + const originalRatio = ratio.width / ratio.height; + + for (let i = 0; i < ASPECT_RATIO.length; i += 1) { + const target = ASPECT_RATIO[i]!; + + const tRatio = target.width / target.height; + const squared = tRatio - originalRatio; + const distance = Math.sqrt(squared * squared); + + if (i === 0) { + nearest = distance; + } else if (distance < nearest) { + id = i; + nearest = distance; + } + } + + return ASPECT_RATIO[id]!; +} + +export function getScaledComponentRatio(ratio: AspectRatio): AspectRatio { + const xScale = 9 / ratio.width; + const yScale = 9 / ratio.height; + const scale = Math.min(xScale, yScale); + return { + width: scale * ratio.width, + height: scale * ratio.height, + }; +} diff --git a/packages/image/src/core/client-only.tsx b/packages/image/src/core/client-only.tsx new file mode 100644 index 000000000..4df147fd9 --- /dev/null +++ b/packages/image/src/core/client-only.tsx @@ -0,0 +1,37 @@ +import type { JSX } from "solid-js"; +import { createSignal, onMount, Show } from "solid-js"; +import { isServer } from "solid-js/web"; + +export const createClientSignal = isServer + ? (): (() => boolean) => () => false + : (): (() => boolean) => { + const [flag, setFlag] = createSignal(false); + + onMount(() => { + setFlag(true); + }); + + return flag; + }; + +export interface ClientOnlyProps { + fallback?: JSX.Element; + children?: JSX.Element; +} + +export const ClientOnly = (props: ClientOnlyProps): JSX.Element => { + const isClient = createClientSignal(); + + return Show({ + keyed: false, + get when() { + return isClient(); + }, + get fallback() { + return props.fallback; + }, + get children() { + return props.children; + }, + }); +}; diff --git a/packages/image/src/core/create-lazy-render.ts b/packages/image/src/core/create-lazy-render.ts new file mode 100644 index 000000000..d5b32d370 --- /dev/null +++ b/packages/image/src/core/create-lazy-render.ts @@ -0,0 +1,62 @@ +import { createEffect, createSignal, onCleanup } from "solid-js"; + +export interface LazyRender { + ref: (value: T) => void; + visible: boolean; +} + +export interface LazyRenderOptions { + refresh?: boolean; +} + +export function createLazyRender( + options?: LazyRenderOptions, +): LazyRender { + const [visible, setVisible] = createSignal(false); + + // We use a reactive ref here so that the component + // re-renders if the host element changes, therefore + // re-evaluating our intersection logic + const [ref, setRef] = createSignal(null); + + createEffect(() => { + // If the host changed, make sure that + // visibility is set to false + setVisible(false); + const shouldRefresh = options?.refresh; + + const current = ref(); + if (!current) { + return; + } + const observer = new IntersectionObserver(entries => { + for (const entry of entries) { + if (shouldRefresh) { + setVisible(entry.isIntersecting); + } else if (entry.isIntersecting) { + // Host intersected, set visibility to true + setVisible(true); + + // Stop observing + observer.disconnect(); + } + } + }); + + observer.observe(current); + + onCleanup(() => { + observer.unobserve(current); + observer.disconnect(); + }); + }); + + return { + ref(value) { + return setRef(() => value); + }, + get visible() { + return visible(); + }, + }; +} diff --git a/packages/image/src/core/index.tsx b/packages/image/src/core/index.tsx new file mode 100644 index 000000000..ee3ce1102 --- /dev/null +++ b/packages/image/src/core/index.tsx @@ -0,0 +1,120 @@ +import type { JSX } from "solid-js"; +import { createMemo, createSignal, For, Show } from "solid-js"; +import { ClientOnly } from "./client-only.tsx"; +import { createLazyRender } from "./create-lazy-render.ts"; +import { + createImageVariants, + mergeImageVariantsByType, + mergeImageVariantsToSrcSet, +} from "./transformer.ts"; +import type { SolidImageSource, SolidImageTransformer, SolidImageVariant } from "./types.ts"; +import { getAspectRatioBoxStyle } from "./utils.ts"; + +import "./styles.css"; + +export interface SolidImageProps { + src: SolidImageSource; + alt: string; + transformer?: SolidImageTransformer; + + onLoad?: () => void; + fallback: (visible: () => boolean, onLoad: () => void) => JSX.Element; + + crossOrigin?: JSX.HTMLCrossorigin | undefined; + fetchPriority?: "high" | "low" | "auto" | undefined; + decoding?: "sync" | "async" | "auto" | undefined; +} + +interface SolidImageSourcesProps extends SolidImageProps { + variants: SolidImageVariant[]; +} + +function SolidImageSources(props: SolidImageSourcesProps): JSX.Element { + const mergedVariants = createMemo(() => { + const types = mergeImageVariantsByType(props.variants); + + const values: [type: string, srcset: string][] = []; + + for (const [key, variants] of types) { + values.push([key, mergeImageVariantsToSrcSet(variants)]); + } + + return values; + }); + + return ( + {([type, srcset]) => } + ); +} + +export function SolidImage(props: SolidImageProps): JSX.Element { + const [showPlaceholder, setShowPlaceholder] = createSignal(true); + const laze = createLazyRender(); + const [defer, setDefer] = createSignal(true); + + function onPlaceholderLoad() { + setDefer(false); + } + + const width = createMemo(() => props.src.width); + const height = createMemo(() => props.src.height); + + return ( +
+
+ + }> + {cb => } + + + } + > + + {props.alt} { + if (!defer()) { + setShowPlaceholder(false); + props.onLoad?.(); + } + }} + style={{ + opacity: showPlaceholder() ? 0 : 1, + }} + crossOrigin={props.crossOrigin} + fetchpriority={props.fetchPriority} + decoding={props.decoding} + /> + + + +
+
+ + {props.fallback(showPlaceholder, onPlaceholderLoad)} + +
+
+ ); +} + +export * from "./types"; diff --git a/packages/image/src/core/styles.css b/packages/image/src/core/styles.css new file mode 100644 index 000000000..a484a6c03 --- /dev/null +++ b/packages/image/src/core/styles.css @@ -0,0 +1,24 @@ +[data-solid-image="blocker"], +[data-solid-image="image"], +[data-solid-image="picture"] { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +[data-solid-image="image"], +[data-solid-image="picture"] { + width: 100%; + height: 100%; + object-fit: contain; + pointer-events: none; + user-select: none; +} + +[data-solid-image="container"] { + width: 100%; + height: 100%; + position: relative; +} \ No newline at end of file diff --git a/packages/image/src/core/transformer.ts b/packages/image/src/core/transformer.ts new file mode 100644 index 000000000..c377997a7 --- /dev/null +++ b/packages/image/src/core/transformer.ts @@ -0,0 +1,117 @@ +import type { + SolidImageFile, + SolidImageFormat, + SolidImageMIME, + SolidImageSource, + SolidImageTransformer, + SolidImageVariant, +} from "./types"; + +const MIME_TO_FORMAT: Record = { + "image/avif": "avif", + "image/jpeg": "jpeg", + "image/png": "png", + "image/webp": "webp", + "image/tiff": "tiff", +}; + +export function getFormatFromMIME(mime: SolidImageMIME): SolidImageFormat { + return MIME_TO_FORMAT[mime]; +} + +const FORMAT_TO_MIME: Record = { + avif: "image/avif", + jpeg: "image/jpeg", + png: "image/png", + webp: "image/webp", + tiff: "image/tiff", +}; + +export function getMIMEFromFormat(format: SolidImageFormat): SolidImageMIME { + return FORMAT_TO_MIME[format]; +} + +const FILE_TO_FORMAT: Record = { + avif: "avif", + jfif: "jpeg", + jpeg: "jpeg", + jpg: "jpeg", + pjp: "jpeg", + pjpeg: "jpeg", + png: "png", + webp: "webp", + tif: "tiff", + tiff: "tiff", +}; + +export function getFormatFromFile(file: SolidImageFile): SolidImageFormat { + return FILE_TO_FORMAT[file]; +} + +const FORMAT_TO_FILES: Record = { + avif: ["avif"], + jpeg: ["jfif", "jpeg", "jpg", "pjp", "pjpeg"], + png: ["png"], + webp: ["webp"], + tiff: ["tif", "tiff"], +}; + +export function getFilesFromFormat(format: SolidImageFormat): SolidImageFile[] { + return FORMAT_TO_FILES[format]; +} + +const FORMAT_TO_OUTPUT: Record = { + avif: "avif", + jpeg: "jpg", + png: "png", + webp: "webp", + tiff: "tiff", +}; + +export function getOutputFileFromFormat(format: SolidImageFormat): SolidImageFile { + return FORMAT_TO_OUTPUT[format]; +} + +function ensureArray(value: T | T[]): T[] { + if (Array.isArray(value)) { + return value; + } + return [value]; +} + +export function createImageVariants( + source: SolidImageSource, + transformer: SolidImageTransformer, +): SolidImageVariant[] { + return ensureArray(transformer.transform(source)); +} + +function variantToSrcSetPart(variant: SolidImageVariant): string { + return variant.path + " " + variant.width + "w"; +} + +export function mergeImageVariantsToSrcSet(variants: SolidImageVariant[]): string { + let result = variantToSrcSetPart(variants[0]!); + + for (let i = 1, len = variants.length; i < len; i++) { + result += "," + variantToSrcSetPart(variants[i]!); + } + + return result; +} + +export function mergeImageVariantsByType( + variants: SolidImageVariant[], +): Map { + const map = new Map(); + + for (let i = 0, len = variants.length; i < len; i++) { + const current = variants[i]!; + + const arr = map.get(current.type) || []; + arr.push(current); + map.set(current.type, arr); + } + + return map; +} diff --git a/packages/image/src/core/types.ts b/packages/image/src/core/types.ts new file mode 100644 index 000000000..c891710bc --- /dev/null +++ b/packages/image/src/core/types.ts @@ -0,0 +1,53 @@ +/** + * List of supported image types + * + * Based on https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Image_types + */ +export type SolidImageMIME = + | "image/avif" + | "image/jpeg" + | "image/png" + | "image/webp" + | "image/tiff"; + +export type SolidImagePNG = "png"; +export type SolidImageAVIF = "avif"; +export type SolidImageJPEG = "jpg" | "jpeg" | "jfif" | "pjpeg" | "pjp"; +export type SolidImageWebP = "webp"; +export type SolidImageTIFF = "tiff" | "tif"; + +export type SolidImageFile = + | SolidImageAVIF + | SolidImageJPEG + | SolidImagePNG + | SolidImageWebP + | SolidImageTIFF; + +export type SolidImageFormat = "avif" | "jpeg" | "png" | "webp" | "tiff"; + +/** + * A variant of an image source. This is used to transform a given source string + * into a element + */ +export interface SolidImageVariant { + path: string; + width: number; + type: SolidImageMIME; +} + +/** + * An image source + */ +export interface SolidImageSource { + source: string; + width: number; + height: number; + options: T; +} + +/** + * Transforms an image source into a set of image variants + */ +export interface SolidImageTransformer { + transform: (source: SolidImageSource) => SolidImageVariant | SolidImageVariant[]; +} diff --git a/packages/image/src/core/utils.ts b/packages/image/src/core/utils.ts new file mode 100644 index 000000000..4b2291eb6 --- /dev/null +++ b/packages/image/src/core/utils.ts @@ -0,0 +1,48 @@ +import type { JSX } from "solid-js"; +import type { AspectRatio } from "./aspect-ratio"; + +function kebabify(str: string): string { + return str + .replace(/([A-Z])([A-Z])/g, "$1-$2") + .replace(/([a-z])([A-Z])/g, "$1-$2") + .replace(/[\s_]+/g, "-") + .toLowerCase(); +} + +export function shimStyle(style: JSX.CSSProperties): JSX.CSSProperties { + const keys = Object.keys(style) as (keyof JSX.CSSProperties)[]; + const newStyle: JSX.CSSProperties = {}; + + for (let i = 0, len = keys.length; i < len; i += 1) { + const key = kebabify(keys[i]!); + newStyle[key as any] = style[keys[i]!]; + } + return newStyle; +} + +export function getAspectRatioBoxStyle(ratio: AspectRatio): JSX.CSSProperties { + return { + position: "relative", + "padding-top": `${(ratio.height * 100) / ratio.width}%`, + width: "100%", + height: "0", + overflow: "hidden", + }; +} + +export function getEmptySVGPlaceholder({ width, height }: AspectRatio): string { + return ``; +} + +export function getEncodedSVG(svg: string): string { + const encodedSVG = encodeURIComponent(svg); + return `data:image/svg+xml,${encodedSVG}`; +} + +export function getEncodedOptionalSVG(ratio: AspectRatio, svg?: string): string { + return getEncodedSVG(svg || getEmptySVGPlaceholder(ratio)); +} + +export function getEmptyImageURL(ratio: AspectRatio): string { + return getEncodedOptionalSVG(ratio); +} diff --git a/packages/image/src/env.d.ts b/packages/image/src/env.d.ts new file mode 100644 index 000000000..e09546c3b --- /dev/null +++ b/packages/image/src/env.d.ts @@ -0,0 +1,15 @@ +declare module "image:*" { + import type { SolidImageProps } from "./core/index.ts"; + + const props: Pick, "src" | "transformer">; + + export default props; +} + +declare module "*?image" { + import type { SolidImageProps } from "./core/index.ts"; + + const props: Pick, "src" | "transformer">; + + export default props; +} diff --git a/packages/image/src/vite/fs.ts b/packages/image/src/vite/fs.ts new file mode 100644 index 000000000..46eefaeaa --- /dev/null +++ b/packages/image/src/vite/fs.ts @@ -0,0 +1,72 @@ +import type { Abortable } from "node:events"; +import type { Mode, ObjectEncodingOptions, OpenMode } from "node:fs"; +import fs from "node:fs/promises"; +import path from "node:path"; +import type { Stream } from "node:stream"; + +export async function removeFile(filePath: string): Promise { + return await fs.rm(filePath, { recursive: true, force: true }); +} + +export async function fileExists(p: string): Promise { + try { + const stat = await fs.stat(p); + + return stat.isFile(); + } catch { + return false; + } +} + +const PATH_FILTER = /[<>:"|?*]/; + +export function checkPath(pth: string) { + if (process.platform === "win32") { + const pathHasInvalidWinCharacters = PATH_FILTER.test(pth.replace(path.parse(pth).root, "")); + + if (pathHasInvalidWinCharacters) { + const error = new Error(`Path contains invalid characters: ${pth}`); + throw error; + } + } +} + +export async function makeDir(dir: string, mode = 0o777) { + checkPath(dir); + return await fs.mkdir(dir, { + mode, + recursive: true, + }); +} + +export async function pathExists(filePath: string) { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } +} + +export async function outputFile( + file: string, + data: + | string + | NodeJS.ArrayBufferView + | Iterable + | AsyncIterable + | Stream, + encoding?: + | (ObjectEncodingOptions & { + mode?: Mode | undefined; + flag?: OpenMode | undefined; + } & Abortable) + | BufferEncoding + | null, +) { + const dir = path.dirname(file); + if (!(await pathExists(dir))) { + await makeDir(dir); + } + return fs.writeFile(file, data, encoding); +} diff --git a/packages/image/src/vite/index.ts b/packages/image/src/vite/index.ts new file mode 100644 index 000000000..02f66e670 --- /dev/null +++ b/packages/image/src/vite/index.ts @@ -0,0 +1,205 @@ +import path from "node:path"; +import type { Plugin } from "vite"; +import { getFilesFromFormat, getMIMEFromFormat, getOutputFileFromFormat } from "../core/transformer.ts"; +import type { SolidImageFile, SolidImageFormat, SolidImageVariant } from "../core/types.ts"; +import { outputFile } from "./fs.ts"; +import { getImageData, transformImage } from "./transformers.ts"; +import xxHash32 from "./xxhash32.ts"; + +const DEFAULT_INPUT: SolidImageFormat[] = ["png", "jpeg", "webp"]; +const DEFAULT_OUTPUT: SolidImageFormat[] = ["png", "jpeg", "webp"]; +const DEFAULT_QUALITY = 0.8; + +type MaybePromise = T | Promise; + +export interface SolidImageOptions { + local?: { + sizes: number[]; + input?: SolidImageFormat[]; + output?: SolidImageFormat[]; + quality: number; + publicPath?: string; + }; + remote?: { + transformURL(url: string): MaybePromise<{ + src: { + source: string; + width: number; + height: number; + }; + variants: SolidImageVariant | SolidImageVariant[]; + }>; + }; +} + +function getValidFileExtensions(formats: SolidImageFormat[]): Set { + const result = new Set(); + for (const format of formats) { + for (const file of getFilesFromFormat(format)) { + result.add(file); + } + } + return result; +} + +function isValidFileExtension(extensions: Set, target: string): target is SolidImageFile { + return extensions.has(target); +} + +async function getImageSource(imagePath: string, relativePath: string): Promise { + // TODO add format variation + const imageData = await getImageData(imagePath); + return ` +import source from ${JSON.stringify(relativePath)}; +export default { + width: ${JSON.stringify(imageData.width)}, + height: ${JSON.stringify(imageData.height)}, + source, +}; +`; +} + +function getImageTransformer(imagePath: string, outputTypes: string[], sizes: number[]): string { + let imported = ""; + let exported = ""; + + for (const format of outputTypes) { + for (const size of sizes) { + const variantName = "variant_" + format + "_" + size; + const importPath = JSON.stringify(imagePath + "?image-" + format + "-" + size); + imported += "import " + variantName + " from " + importPath + ";\n"; + exported += variantName + ","; + } + } + + return ( + imported + + "const variants = [" + + exported + + "];\n" + + "export default { transform() { return variants; }};" + ); +} + +function getImageVariant(imagePath: string, target: SolidImageFormat, size: number): string { + return `import source from ${JSON.stringify(imagePath + "?image-raw-" + target + "-" + size)}; +export default { + width: ${size}, + type: '${getMIMEFromFormat(target)}', + path: source, +};`; +} + +function getImageEntryPoint(imagePath: string): string { + return `import src from ${JSON.stringify(imagePath + "?image-source")}; +import transformer from ${JSON.stringify(imagePath + "?image-transformer")}; + +export default { src, transformer }; +`; +} + +const LOCAL_PATH = /\?image(-[a-z]+(-[0-9]+)?)?/; +const REMOTE_PATH = "image:"; + +export const imagePlugin = (options: SolidImageOptions) => { + const plugins: Plugin[] = []; + if (options.remote) { + const transformUrl = options.remote.transformURL; + plugins.push({ + name: "solid-start:image/remote", + enforce: "pre", + resolveId(id) { + if (id.startsWith(REMOTE_PATH)) { + return id; + } + return null; + }, + async load(id) { + if (id.startsWith(REMOTE_PATH)) { + const param = id.substring(REMOTE_PATH.length); + + const result = await transformUrl(param); + + return `const VARIANTS = ${JSON.stringify(result.variants)}; +export default { + src: ${JSON.stringify(result.src)}, + transformer: { + transform() { + return VARIANTS; + }, + }, +};`; + } + return null; + }, + }); + } + if (options.local) { + const inputFormat = options.local.input ?? DEFAULT_INPUT; + const outputFormat = options.local.output ?? DEFAULT_OUTPUT; + const quality = options.local.quality ?? DEFAULT_QUALITY; + const sizes = options.local.sizes; + const publicPath = options.local.publicPath ?? "dist"; + + const validInputFileExtensions = getValidFileExtensions(inputFormat); + + plugins.push({ + name: "solid-start:image/local", + enforce: "pre", + resolveId(id, importer) { + if (LOCAL_PATH.test(id) && importer) { + return path.join(path.dirname(importer), id); + } + return null; + }, + async load(id) { + if (id.startsWith("\0")) { + return null; + } + const { dir, name, ext } = path.parse(id); + const [actualExtension, condition] = ext.substring(1).split("?"); + // Check if extension is valid + if (!isValidFileExtension(validInputFileExtensions, actualExtension!)) { + return null; + } + if (!condition) { + return null; + } + const originalPath = `${dir}/${name}.${actualExtension}`; + const relativePath = `./${name}.${actualExtension}`; + // Get the true source + if (condition.startsWith("image-source")) { + return await getImageSource(originalPath, relativePath); + } + // Get the transformer file + if (condition.startsWith("image-transformer")) { + return getImageTransformer(relativePath, outputFormat, sizes); + } + // Image transformer variant + if (condition.startsWith("image-raw")) { + const [, , format, size] = condition.split("-"); + const hash = xxHash32(originalPath).toString(16); + const filename = `i-${hash}-${size}.${getOutputFileFromFormat(format as SolidImageFormat)}`; + const image = transformImage(originalPath, format as SolidImageFormat, +size!, quality); + const buffer = await image.toBuffer(); + const basePath = path.join(".image", filename); + const targetPath = path.join(publicPath, basePath); + await outputFile(targetPath, buffer); + return `export default "/${basePath}"`; + } + // Image transformer variant + if (condition.startsWith("image-")) { + const [, format, size] = condition.split("-"); + + return getImageVariant(relativePath, format as SolidImageFormat, +size!); + } + if (condition.startsWith("image")) { + return getImageEntryPoint(relativePath); + } + return null; + }, + }); + } + + return plugins; +}; diff --git a/packages/image/src/vite/transformers.ts b/packages/image/src/vite/transformers.ts new file mode 100644 index 000000000..762d59ce4 --- /dev/null +++ b/packages/image/src/vite/transformers.ts @@ -0,0 +1,46 @@ +import sharp from "sharp"; +import type { SolidImageFormat } from "../core/types.ts"; + +export function transformImage( + originalPath: string, + targetFormat: SolidImageFormat, + size: number, + quality: number, +) { + const input = sharp(originalPath); + switch (targetFormat) { + case "avif": + return input.resize(size).avif({ + quality, + }); + case "jpeg": + return input.resize(size).jpeg({ + quality, + }); + case "png": + return input.resize(size).png({ + quality, + }); + case "webp": + return input.resize(size).webp({ + quality, + }); + case "tiff": + return input.resize(size).tiff({ + quality, + }); + } +} + +interface ImageData { + width: number; + height: number; +} + +export async function getImageData(originalPath: string): Promise { + const result = await sharp(originalPath).metadata(); + return { + width: result.width || 0, + height: result.height || 0, + }; +} diff --git a/packages/image/src/vite/xxhash32.ts b/packages/image/src/vite/xxhash32.ts new file mode 100644 index 000000000..0cae220f3 --- /dev/null +++ b/packages/image/src/vite/xxhash32.ts @@ -0,0 +1,202 @@ +// @ts-nocheck +/** + * Copyright (c) 2019 Jason Dent + * https://github.com/Jason3S/xxhash + */ +const PRIME32_1 = 2654435761; +const PRIME32_2 = 2246822519; +const PRIME32_3 = 3266489917; +const PRIME32_4 = 668265263; +const PRIME32_5 = 374761393; + +function toUtf8(text: string): Uint8Array { + const bytes: number[] = []; + for (let i = 0, n = text.length; i < n; ++i) { + const c = text.charCodeAt(i); + if (c < 0x80) { + bytes.push(c); + } else if (c < 0x800) { + bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); + } else if (c < 0xd800 || c >= 0xe000) { + bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + } else { + const cp = 0x10000 + (((c & 0x3ff) << 10) | (text.charCodeAt(++i) & 0x3ff)); + bytes.push( + 0xf0 | ((cp >> 18) & 0x7), + 0x80 | ((cp >> 12) & 0x3f), + 0x80 | ((cp >> 6) & 0x3f), + 0x80 | (cp & 0x3f), + ); + } + } + return new Uint8Array(bytes); +} +/** + * + * @param buffer - byte array or string + * @param seed - optional seed (32-bit unsigned); + */ +export default function xxHash32(buffer: Uint8Array | string, seed = 0): number { + buffer = typeof buffer === "string" ? toUtf8(buffer) : buffer; + const b = buffer; + + /* + Step 1. Initialize internal accumulators + Each accumulator gets an initial value based on optional seed input. + Since the seed is optional, it can be 0. + ``` + u32 acc1 = seed + PRIME32_1 + PRIME32_2; + u32 acc2 = seed + PRIME32_2; + u32 acc3 = seed + 0; + u32 acc4 = seed - PRIME32_1; + ``` + Special case : input is less than 16 bytes + When input is too small (< 16 bytes), the algorithm will not process any stripe. + Consequently, it will not make use of parallel accumulators. + In which case, a simplified initialization is performed, using a single accumulator : + u32 acc = seed + PRIME32_5; + The algorithm then proceeds directly to step 4. + */ + + let acc = (seed + PRIME32_5) & 0xffffffff; + let offset = 0; + + if (b.length >= 16) { + const accN = [ + (seed + PRIME32_1 + PRIME32_2) & 0xffffffff, + (seed + PRIME32_2) & 0xffffffff, + (seed + 0) & 0xffffffff, + (seed - PRIME32_1) & 0xffffffff, + ]; + + /* + Step 2. Process stripes + A stripe is a contiguous segment of 16 bytes. It is evenly divided into 4 lanes, + of 4 bytes each. The first lane is used to update accumulator 1, the second lane + is used to update accumulator 2, and so on. Each lane read its associated 32-bit + value using little-endian convention. For each {lane, accumulator}, the update + process is called a round, and applies the following formula : + ``` + accN = accN + (laneN * PRIME32_2); + accN = accN <<< 13; + accN = accN * PRIME32_1; + ``` + This shuffles the bits so that any bit from input lane impacts several bits in + output accumulator. All operations are performed modulo 2^32. + Input is consumed one full stripe at a time. Step 2 is looped as many times as + necessary to consume the whole input, except the last remaining bytes which cannot + form a stripe (< 16 bytes). When that happens, move to step 3. + */ + + const b = buffer; + const limit = b.length - 16; + let lane = 0; + for (offset = 0; (offset & 0xfffffff0) <= limit; offset += 4) { + const i = offset; + const laneN0 = b[i + 0] + (b[i + 1] << 8); + const laneN1 = b[i + 2] + (b[i + 3] << 8); + const laneNP = laneN0 * PRIME32_2 + ((laneN1 * PRIME32_2) << 16); + let acc = (accN[lane] + laneNP) & 0xffffffff; + acc = (acc << 13) | (acc >>> 19); + const acc0 = acc & 0xffff; + const acc1 = acc >>> 16; + accN[lane] = (acc0 * PRIME32_1 + ((acc1 * PRIME32_1) << 16)) & 0xffffffff; + lane = (lane + 1) & 0x3; + } + + /* + Step 3. Accumulator convergence + All 4 lane accumulators from previous steps are merged to produce a + single remaining accumulator + of same width (32-bit). The associated formula is as follows : + ``` + acc = (acc1 <<< 1) + (acc2 <<< 7) + (acc3 <<< 12) + (acc4 <<< 18); + ``` + */ + acc = + (((accN[0] << 1) | (accN[0] >>> 31)) + + ((accN[1] << 7) | (accN[1] >>> 25)) + + ((accN[2] << 12) | (accN[2] >>> 20)) + + ((accN[3] << 18) | (accN[3] >>> 14))) & + 0xffffffff; + } + + /* + Step 4. Add input length + The input total length is presumed known at this stage. + This step is just about adding the length to + accumulator, so that it participates to final mixing. + ``` + acc = acc + (u32)inputLength; + ``` + */ + acc = (acc + buffer.length) & 0xffffffff; + + /* + Step 5. Consume remaining input + There may be up to 15 bytes remaining to consume from the input. + The final stage will digest them according + to following pseudo-code : + ``` + while (remainingLength >= 4) { + lane = read_32bit_little_endian(input_ptr); + acc = acc + lane * PRIME32_3; + acc = (acc <<< 17) * PRIME32_4; + input_ptr += 4; remainingLength -= 4; + } + ``` + This process ensures that all input bytes are present in the final mix. + */ + + const limit = buffer.length - 4; + for (; offset <= limit; offset += 4) { + const i = offset; + const laneN0 = b[i + 0] + (b[i + 1] << 8); + const laneN1 = b[i + 2] + (b[i + 3] << 8); + const laneP = laneN0 * PRIME32_3 + ((laneN1 * PRIME32_3) << 16); + acc = (acc + laneP) & 0xffffffff; + acc = (acc << 17) | (acc >>> 15); + acc = ((acc & 0xffff) * PRIME32_4 + (((acc >>> 16) * PRIME32_4) << 16)) & 0xffffffff; + } + + /* + ``` + while (remainingLength >= 1) { + lane = read_byte(input_ptr); + acc = acc + lane * PRIME32_5; + acc = (acc <<< 11) * PRIME32_1; + input_ptr += 1; remainingLength -= 1; + } + ``` + */ + + for (; offset < b.length; ++offset) { + const lane = b[offset]; + acc += lane * PRIME32_5; + acc = (acc << 11) | (acc >>> 21); + acc = ((acc & 0xffff) * PRIME32_1 + (((acc >>> 16) * PRIME32_1) << 16)) & 0xffffffff; + } + + /* + Step 6. Final mix (avalanche) + The final mix ensures that all input bits have a chance to impact any bit in + the output digest, resulting in an unbiased distribution. This is also called + avalanche effect. + ``` + acc = acc xor (acc >> 15); + acc = acc * PRIME32_2; + acc = acc xor (acc >> 13); + acc = acc * PRIME32_3; + acc = acc xor (acc >> 16); + ``` + */ + + acc ^= acc >>> 15; + acc = (((acc & 0xffff) * PRIME32_2) & 0xffffffff) + (((acc >>> 16) * PRIME32_2) << 16); + acc ^= acc >>> 13; + acc = (((acc & 0xffff) * PRIME32_3) & 0xffffffff) + (((acc >>> 16) * PRIME32_3) << 16); + acc ^= acc >>> 16; + + // turn any negatives back into a positive number; + return acc < 0 ? acc + 4294967296 : acc; +} diff --git a/packages/image/tsconfig.build.json b/packages/image/tsconfig.build.json new file mode 100644 index 000000000..a632032dc --- /dev/null +++ b/packages/image/tsconfig.build.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "isolatedModules": true, + "declaration": true, + "skipLibCheck": true, + "types": ["vite/client"], + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "include": ["./src", "./env.d.ts"], + "exclude": ["./dist", "**/*.spec.ts"] +} diff --git a/packages/image/tsconfig.json b/packages/image/tsconfig.json new file mode 100644 index 000000000..d8faaf508 --- /dev/null +++ b/packages/image/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.build.json" +} diff --git a/packages/image/tsconfig.node.json b/packages/image/tsconfig.node.json new file mode 100644 index 000000000..a44d6036c --- /dev/null +++ b/packages/image/tsconfig.node.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + "allowJs": true, + "isolatedModules": true, + "declaration": true, + "skipLibCheck": true, + "types": ["node", "vite/client"], + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "include": ["./src", "./env.d.ts"], + "exclude": ["./dist", "**/*.spec.ts"] +} diff --git a/packages/image/tsdown.config.ts b/packages/image/tsdown.config.ts new file mode 100644 index 000000000..a68648410 --- /dev/null +++ b/packages/image/tsdown.config.ts @@ -0,0 +1,34 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig([ + { + entry: "src/core/index.tsx", + format: ["esm"], + dts: true, + tsconfig: "tsconfig.build.json", + clean: true, + platform: "browser", + outExtensions: () => ({ + js: ".jsx", + }), + css: { + minify: true, + }, + exports: true, + }, + { + entry: { + vite: "src/vite/index.ts", + env: "src/env.d.ts", + }, + format: ["esm"], + dts: true, + tsconfig: "tsconfig.node.json", + clean: false, + platform: "node", + outExtensions: () => ({ + js: ".js", + }), + exports: true, + }, +]); diff --git a/packages/image/vitest.config.ts b/packages/image/vitest.config.ts new file mode 100644 index 000000000..021686dfb --- /dev/null +++ b/packages/image/vitest.config.ts @@ -0,0 +1,27 @@ +import solid from "vite-plugin-solid"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + plugins: [ + solid({ ssr: true }), + // vite-plugin-solid automatically injects @testing-library/jest-dom into + // setupFiles when it detects the module in pnpm's store. Because the image + // package does not depend on jest-dom, the import fails at runtime. + // Strip the injected entry so vitest never tries to load it. + { + name: "strip-jest-dom-setup", + config(config) { + const files = config.test?.setupFiles; + if (Array.isArray(files)) { + config.test!.setupFiles = files.filter( + f => typeof f !== "string" || !f.includes("jest-dom"), + ); + } + }, + }, + ], + test: { + globals: true, + environment: "node", + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f8caf4dd..6c9871a78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -256,6 +256,9 @@ importers: apps/tests: dependencies: + '@solidjs/image': + specifier: workspace:* + version: link:../../packages/image '@solidjs/meta': specifier: ^0.29.4 version: 0.29.4(solid-js@1.9.11) @@ -315,6 +318,34 @@ importers: specifier: ^1.58.2 version: 1.58.2 + packages/image: + dependencies: + sharp: + specifier: ^0.35.3 + version: 0.35.3(@types/node@25.5.0) + devDependencies: + '@tsdown/css': + specifier: ^0.22.12 + version: 0.22.12(jiti@2.7.0)(postcss@8.5.19)(tsdown@0.22.12) + '@types/node': + specifier: ^25.5.0 + version: 25.5.0 + solid-js: + specifier: ^1.9.9 + version: 1.9.11 + tsdown: + specifier: ^0.22.12 + version: 0.22.12(@tsdown/css@0.22.12)(typescript@7.0.2) + vite: + specifier: ^8.1.5 + version: 8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0) + vite-plugin-solid: + specifier: ^2.11.11 + version: 2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + vitest: + specifier: ^4.0.10 + version: 4.1.0(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(@vitest/ui@4.1.0)(jsdom@28.1.0)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + packages/start: dependencies: '@babel/core': @@ -614,9 +645,15 @@ packages: '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} @@ -794,6 +831,168 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.3': + resolution: {integrity: sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.3': + resolution: {integrity: sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.3': + resolution: {integrity: sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.2': + resolution: {integrity: sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.2': + resolution: {integrity: sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.2': + resolution: {integrity: sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.3.2': + resolution: {integrity: sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.3.2': + resolution: {integrity: sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.3.2': + resolution: {integrity: sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.3.2': + resolution: {integrity: sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.3.2': + resolution: {integrity: sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + resolution: {integrity: sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.3.2': + resolution: {integrity: sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.35.3': + resolution: {integrity: sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.35.3': + resolution: {integrity: sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.35.3': + resolution: {integrity: sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.35.3': + resolution: {integrity: sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.35.3': + resolution: {integrity: sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.35.3': + resolution: {integrity: sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.35.3': + resolution: {integrity: sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.35.3': + resolution: {integrity: sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.35.3': + resolution: {integrity: sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.3': + resolution: {integrity: sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.3': + resolution: {integrity: sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.3': + resolution: {integrity: sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.3': + resolution: {integrity: sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -1013,6 +1212,9 @@ packages: '@oxc-project/types@0.139.0': resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + '@oxc-project/types@0.140.0': + resolution: {integrity: sha512-h5LUOzGArYemnW1NMz/DuuQhBi96J6JL2Bk8zE4kvqxB5Sg3jxmCiH4uyOWHDkiKSt5vWlG4FIwCR/DbstcNRQ==} + '@oxfmt/binding-android-arm-eabi@0.36.0': resolution: {integrity: sha512-Z4yVHJWx/swHHjtr0dXrBZb6LxS+qNz1qdza222mWwPTUK4L790+5i3LTgjx3KYGBzcYpjaiZBw4vOx94dH7MQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1240,36 +1442,69 @@ packages: '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + '@rolldown/binding-android-arm64@1.1.5': resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.2.0': + resolution: {integrity: sha512-9yB1l95IrJuNGDFdOYe79vdApdz6WWBCObE+rQ2LUliYUlcyFwSYIb2xb5/Ifw7dAtMy2ZqNyd8QTSOc7duAKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.1.5': resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.2.0': + resolution: {integrity: sha512-pexNaW9ACLUOaBITOpU6qVu4VrsOFIjTv6bzgu0YUATo4eUJx0V605PxwZfndpPOn0ilqGqvGQ0M8UW0IE24jg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.1.5': resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.2.0': + resolution: {integrity: sha512-NqKYaq0355ZmNMG4QGpxtEDxsc7tGDhjhCm4PpE0cwnBW+5Il95LJyq414niEiaKLVjnVHBEjSo1wngKxJNiFw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.1.5': resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.2.0': + resolution: {integrity: sha512-3vPoHzh6eBTz9IbB0/qZdSr0Qeks2echn+I4cHu2joV74VriPDdldswksEDzrl1mBB+oPRi+67+3Ib59paxIPQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.2.0': + resolution: {integrity: sha512-E6NNefZ1bUVmKJq2tJkf45J4Zyczj7qm9rUT7NY+Xo2474Y13qWAwc2tvBt0BAVbmtXR1llkxXg0Ou1jbDf2SQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.1.5': resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1277,6 +1512,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-arm64-gnu@1.2.0': + resolution: {integrity: sha512-D+TgkdgM1vu+7/Fpf8+v0ARW+RXEP9Ccazgm8zQ4JFFd9Q7SrYQ2TakU5S5ihazQDgpKyAgZDOcIFsvoHmTZ8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-arm64-musl@1.1.5': resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1284,6 +1526,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-arm64-musl@1.2.0': + resolution: {integrity: sha512-wUqdwJBbAv0APN87GecstdMUtLjjNTs0hBALpxETD73mccFxdmt/XeizXDtN5RAlBwNKmI+Tg+blect2G+8IeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rolldown/binding-linux-ppc64-gnu@1.1.5': resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1291,6 +1540,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-ppc64-gnu@1.2.0': + resolution: {integrity: sha512-9DtF35qR9/NrfhM4oxLplCzVVjE+KKm8Pjemi0i/sdhAWkUasjmSo8WTTubNJClhSHCfyk2yeyoXDQEDPtDAAw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.1.5': resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1298,6 +1554,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.2.0': + resolution: {integrity: sha512-RzuHrBh8X8Hntd2N4VR02QGEciq/9JhcZoTpR/Cee6otRrlILGCf3cg2ygHuih+ZebUnWmMrDX6ITI85btO6rQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.1.5': resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1305,6 +1568,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.2.0': + resolution: {integrity: sha512-MK7L0018jjh1jR3mh21G2j1zAVcpscJBlPo2z19pRjv2XOYGRhaV4LyiD8HO6nCDdZln9IFgCMIV5yt4E3klGQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-musl@1.1.5': resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1312,29 +1582,59 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-x64-musl@1.2.0': + resolution: {integrity: sha512-gyrxLQ9NfGb/9LoVnC4kb9miUghw1mghnkfYvNHSnVIXriabnfgGPUP4RLcJm87q3KgYz4FYUG8IDiWUT+CpSw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@rolldown/binding-openharmony-arm64@1.1.5': resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.2.0': + resolution: {integrity: sha512-/6VFMQGRmrhP77KXDC+StIxGzcNp5JOIyYtw0CQ8gPlzhpiIRucYfoM5FaFamHd5BJYIdH86yfP46l1p3WdrFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.1.5': resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.2.0': + resolution: {integrity: sha512-rwdbUL465kisF24WEJLvP3JrEG6E5GRuIHt5wpMwHGERtHe4Wm2CIvtf5gTBgr2tGOHKh5NdKEAFS2VkOPE91g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.1.5': resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.2.0': + resolution: {integrity: sha512-+5suHwRiKGmhwyUaNT8a5QbrBvLFh2DbO910TEmGRH1aSxwrCezodvGQnulv4uiWEIv1Kq4ypRsJ5+O+ry1DiA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.1.5': resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.2.0': + resolution: {integrity: sha512-WfFv6/qGufotqBSBzBYwgpCkJBk8Nj7697LL9vTz/XWc67e0r3oewu8iMRwQj3AUL45GVD7wVsPjCsAAtW66Wg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} @@ -1558,6 +1858,28 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' + '@tsdown/css@0.22.12': + resolution: {integrity: sha512-y7UMif5Fi96wZ8h9nRUIfxicLdmpIFl89CZNRi3suvyZCfBfPN9zQgUAUjtDK5yYKlGJlJM81LYf/XNLJ0mxWA==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + postcss: ^8.4.0 + postcss-import: ^16.0.0 + postcss-modules: ^6.0.0 + sass: '*' + sass-embedded: '*' + tsdown: 0.22.12 + peerDependenciesMeta: + postcss: + optional: true + postcss-import: + optional: true + postcss-modules: + optional: true + sass: + optional: true + sass-embedded: + optional: true + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -1778,6 +2100,137 @@ packages: '@vitest/utils@4.1.0': resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} + '@yuku-codegen/binding-darwin-arm64@0.6.12': + resolution: {integrity: sha512-C9rpGA8S8MTze6+EeM9wVu63OXyfzvzhVaBW7gvvS+X36uQc3DZ3qAN5kuXkRVaVN+pJqb+maIKEjnJ0k9lwmw==} + cpu: [arm64] + os: [darwin] + + '@yuku-codegen/binding-darwin-x64@0.6.12': + resolution: {integrity: sha512-YV2jLF2hJtkRjw50szArmyl19DI2s50Y0v+UTb6vADaDTts0kCP/ZJ5bvTX8XuX/5RW7kTERZx/PI+w/9lnAKw==} + cpu: [x64] + os: [darwin] + + '@yuku-codegen/binding-freebsd-x64@0.6.12': + resolution: {integrity: sha512-hNAMOQhYuW9f3wIPwP7anyvr+sBBwtEUSTEOD7BYwcuay9f+wjAYM+UBiL2wiJ/4L/0yV0SNc1fF/N+BWEKqKw==} + cpu: [x64] + os: [freebsd] + + '@yuku-codegen/binding-linux-arm-gnu@0.6.12': + resolution: {integrity: sha512-ClOiLpyofntNGZzQ3BxbwkNe92J911t9C2R3s6HwQ2yH0lRLS88DUey7+xQNu4az96T1PEc+pR9ZIP/GzOCHNg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm-musl@0.6.12': + resolution: {integrity: sha512-8LnO3kYIEpm13Gj0RDfM2hciPxagTZWJcB6RM6DZBEF+GJYsmOII90F0UulLzn/l2bLY5oVPMPgF0Rn29fY2jw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-arm64-gnu@0.6.12': + resolution: {integrity: sha512-GdETEflvfkPtdEpzT6QBAucsMAt7wiLYMk4+w1qA34J02OAo11BsTJrxUkPaQ4z/S6KkX3IfK5Ud9rCAyelnCg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-arm64-musl@0.6.12': + resolution: {integrity: sha512-ztFEUChX/sK9B0DH6//g4/QYl4dvTs3BOE+YMVt7pxg9G9fzBcV1vMZdROuXXNGWp6/hU56kTSZ8t4uW/0qpjw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-linux-x64-gnu@0.6.12': + resolution: {integrity: sha512-aDZEat8bARks9upxd8Joi7LGatX2B/TwlnXIdbMCGlONK3WAEroihY1SUyGlf02niU1OyBidhlN7l3l+LByMxw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-codegen/binding-linux-x64-musl@0.6.12': + resolution: {integrity: sha512-IEuUSS04RsAx+d3PpAMAgCmEC0PzAuk5cath2Zk/KGe/te1zwTjZGxbBJM7n+0APaHB+IhUEMgm0bcjIAweEVQ==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-codegen/binding-win32-arm64@0.6.12': + resolution: {integrity: sha512-0HHfyj/TbuFN09QFyypZ4+5vqmxeOYmEdFGuDnsq0KJdxnTeH0Fbqh1Bn7ZRV89IRzi8Fm7zSkb3mMJIS8Tk4g==} + cpu: [arm64] + os: [win32] + + '@yuku-codegen/binding-win32-x64@0.6.12': + resolution: {integrity: sha512-ZItYULslPqB9S7b53v23IeUcPCd5NeW+5+BoMyCY5/AEsMADKdznZXd4ELO1TZxv+0oA55g3iAOC9GWgigCsZA==} + cpu: [x64] + os: [win32] + + '@yuku-parser/binding-darwin-arm64@0.6.12': + resolution: {integrity: sha512-8nsmwwzuh2YCmJ3LT26RQp1tvS6FzGC2+XP6wFz6TNaBtQje+QgwkVdKVuECVWNHPFmuSTHonOnSzX0QirHqvQ==} + cpu: [arm64] + os: [darwin] + + '@yuku-parser/binding-darwin-x64@0.6.12': + resolution: {integrity: sha512-URbCSLE/B0jWnB94RpOxak2TM0GvRLUmArtfb/UmxtQfNOLed4LKK0jUv68IMAL9lOmJlvOBH9UwOyeuLEfuvA==} + cpu: [x64] + os: [darwin] + + '@yuku-parser/binding-freebsd-x64@0.6.12': + resolution: {integrity: sha512-VmiuUxe6uUe2c5tmBnIdZ+/LQ++ioKIZcCP/zMB8E7tuUaQesvMEPOS8ZQrUohFY2VlrKroiATaB1l9KJw15Zw==} + cpu: [x64] + os: [freebsd] + + '@yuku-parser/binding-linux-arm-gnu@0.6.12': + resolution: {integrity: sha512-q9WcllopGo8W9qzMz4Aahew3z/rLxiZFpmVoZQaLjwaO9EDW3sULue2zJ9agvVMf1NzRXircauTUrPZ4EZ74cQ==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm-musl@0.6.12': + resolution: {integrity: sha512-DcEO5Ywaw4r5keOonqdvuQdKNqN+VipJrdiC+jakOwiy1NLSbseylyufheg6uafviMuGn2to1oWw1FXD6Y4ztQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-arm64-gnu@0.6.12': + resolution: {integrity: sha512-zUFJar5Y9On9eIAw6kLxCmuAirGUjC3pyLdxrHC1dYueIMzZDrBa0hyzHRC7Nybr6R7COQ2U0oVZuZcl46RboA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-arm64-musl@0.6.12': + resolution: {integrity: sha512-nPkXLUBQ0GiU0+BQByTb5M+a9wnCfM0SSBmIHYJ2KtaRUYmOruMut4AyBOVwIE2mbPeiOYcrWpMkseeFJ7Wonw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-linux-x64-gnu@0.6.12': + resolution: {integrity: sha512-XucJKw1k2nyfQoiBJiQJm1q3n6K8rCDvhQpF4XRoIzTQNhAV2sldEayJvCAsTS4orCHcsDqrDrBVRMRg8ExXPw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@yuku-parser/binding-linux-x64-musl@0.6.12': + resolution: {integrity: sha512-+p17OXn9xf4zePE3EEh1NriHv5GtfBJ8dEeaGVHUQNo5rkzDzcMH3PrMUD00RqWrWci5BfY7jMhMxgUye+NArA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@yuku-parser/binding-win32-arm64@0.6.12': + resolution: {integrity: sha512-BF6hLXQzaKj5Rn5r1QrCu6GBqdkjOOb8qpzWoKdG18QVhr/pnMPJpFl9isMg20orlOM+Kl/mU6XfzljgmzbocQ==} + cpu: [arm64] + os: [win32] + + '@yuku-parser/binding-win32-x64@0.6.12': + resolution: {integrity: sha512-Qv4Lwg3pvLJrLg+JlB71Xuz3kIxEg/S402jpoLSlchvUkQKTzO+dii0+97FZfQsN67pqqssenhezg5iLNrdw1w==} + cpu: [x64] + os: [win32] + + '@yuku-toolchain/types@0.5.43': + resolution: {integrity: sha512-kSpvPntnXw5+lYjO71ffBEnQ5ycQ74KGIYknh0TS4xeyCuBkOqxyJumxZkMhLBBUCLjDAbx2+Icnr3Zh4ftjpQ==} + + '@yuku-toolchain/types@0.6.11': + resolution: {integrity: sha512-i1JYFNJaKNCgyJ/nVoR8GK7wvlXF+ShYzFHBauWcvg8IoiXInK7pVziHcgNz/MWLPNr/Mb/CtmXccrJMkKqSHQ==} + + '@yuku-toolchain/types@0.6.8': + resolution: {integrity: sha512-AbUd1775RVkOxJkh8hkldIWoU6kRMTCsZFSZq8Ny53q7GkbaVe5UCfleNZ3RWCoz/ZKE8qwfeB7Cj0xqhLWsKA==} + acorn@8.16.0: resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} @@ -1799,6 +2252,10 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansis@4.3.1: + resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1879,6 +2336,10 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -2036,6 +2497,9 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -2078,12 +2542,25 @@ packages: resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} engines: {node: '>=12'} + dts-resolver@3.0.0: + resolution: {integrity: sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==} + engines: {node: ^22.18.0 || >=24.0.0} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + electron-to-chromium@1.5.313: resolution: {integrity: sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==} emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + enhanced-resolve@5.21.6: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} @@ -2216,6 +2693,10 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-tsconfig@5.0.0-beta.5: + resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} + engines: {node: '>=20.20.0'} + giget@2.0.0: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true @@ -2318,6 +2799,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + import-without-cache@0.4.0: + resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} + engines: {node: ^22.18.0 || >=24.0.0} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -2702,6 +3187,10 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + ocache@0.1.5: resolution: {integrity: sha512-kNNnkkVQup/QDvmTz8Q84wc2ntiyoVHDxa6eHWKt5qdGAmFRBIxy83rxgCYEjW0x06UJ9E3P6VgM2yY4rOBH4w==} @@ -2908,6 +3397,9 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2961,6 +3453,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -2970,11 +3465,35 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rolldown-plugin-dts@0.27.11: + resolution: {integrity: sha512-DnBl4USSTV/h/sgy//QjCLHVo2JypE/52P5QugGeYaEqZmjBz/FYESOld0MhyIhul2U3Vsh41K63UWGHhJU/qQ==} + engines: {node: ^22.18.0 || >=24.11.0} + peerDependencies: + '@typescript/native-preview': '*' + '@volar/typescript': ~2.4.0 + rolldown: ^1.0.0 + typescript: ^5.0.0 || ^6.0.0 || ~7.0.0 + vue-tsc: ~3.2.0 || ~3.3.0 + peerDependenciesMeta: + '@typescript/native-preview': + optional: true + '@volar/typescript': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + rolldown@1.1.5: resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.2.0: + resolution: {integrity: sha512-u7tgm5l4Yw1iTqUL4EcYOAt7fFvCgQMLeidrnD4GALlC6aOznCjezYajgxeyKw27u0Q5N7fwgCzjVyPIWzwuBA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rou3@0.8.1: resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} @@ -3000,6 +3519,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + seroval-plugins@1.5.1: resolution: {integrity: sha512-4FbuZ/TMl02sqv0RTFexu0SP6V+ywaIe5bAWCCEik0fk17BhALgwvUDVF7e3Uvf9pxmwCEJsRPmlkUE6HdzLAw==} engines: {node: '>=10'} @@ -3014,6 +3538,15 @@ packages: resolution: {integrity: sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==} engines: {node: '>=10'} + sharp@0.35.3: + resolution: {integrity: sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q==} + engines: {node: '>=20.9.0'} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -3204,6 +3737,10 @@ packages: resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} engines: {node: '>=18'} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -3246,12 +3783,50 @@ packages: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsdown@0.22.12: + resolution: {integrity: sha512-IzGRfGwSsufXJWOcQ0tmECKMG/dbxhUwDOySTS7JE1AM8pkB9+bpcTPs8bTxxSNrSvGocSczrBlOh97tZObq9Q==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.22.12 + '@tsdown/exe': 0.22.12 + '@vitejs/devtools': '*' + publint: ^0.3.8 + tsx: '*' + typescript: ^5.0.0 || ^6.0.0 || ^7.0.0 + unplugin-unused: ^0.5.0 + unrun: '*' + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + tsx: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + unrun: + optional: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -3263,6 +3838,9 @@ packages: ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} @@ -3429,6 +4007,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + verkit@0.1.0: + resolution: {integrity: sha512-hot+EjuRuW/QsivBRTVKlL5FUVPIGpnjJv0fzuyYyD9Bqaj3Kx2KoSw1WVIoCTOhFkmNJZ0fhl66JdsXjWXztw==} + engines: {node: ^22.18.0 || ^24.11.0 || >=26.0.0} + vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} @@ -3488,6 +4070,49 @@ packages: yaml: optional: true + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.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 + vitefu@1.1.2: resolution: {integrity: sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==} peerDependencies: @@ -3579,6 +4204,18 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yuku-ast@0.1.7: + resolution: {integrity: sha512-2RiMEWv500TixY5rJy6OZd4fSy9WYZKWh6gGbIJ7y7vAGcuCugWOWwOLGaQcRZrXcPUfqtLtvpaJ3SdXtWlhKA==} + + yuku-ast@0.6.11: + resolution: {integrity: sha512-ZfXkFYVsDewS45+kv3WiA/qNB73CRfxFDEQwfnRMUAR4AD5zRI7PRqxmI2U3Jz/oG41GneTVW6mxDOQal0lgeA==} + + yuku-codegen@0.6.12: + resolution: {integrity: sha512-5cAJedx9MdKf/ngFpMYH9B1DKaB3FVJOPncl80M+8nNS2rEvrta17N59ryEVIewuHYS81o4XDb4Hg5/5hBdF7A==} + + yuku-parser@0.6.12: + resolution: {integrity: sha512-A1+KVTvdm1pQmrl/cS5JlqFvUclKpM8I5MvAOoQnYSGmmJBVDKN24HNXiZmxE8Ndsl4g3qBliZBAxq8/J4pKxA==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -3903,11 +4540,22 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 @@ -3985,24 +4633,130 @@ snapshots: '@esbuild/win32-arm64@0.27.4': optional: true - '@esbuild/win32-ia32@0.27.4': + '@esbuild/win32-ia32@0.27.4': + optional: true + + '@esbuild/win32-x64@0.27.4': + optional: true + + '@exodus/bytes@1.15.0': {} + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/utils@0.2.11': {} + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.2 + optional: true + + '@img/sharp-darwin-x64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.2 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.3': + dependencies: + '@img/sharp-wasm32': 0.35.3 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.2': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.2': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.2': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.2': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.2': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.2': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.2': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.2': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.2': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.2': + optional: true + + '@img/sharp-linux-arm64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.2 + optional: true + + '@img/sharp-linux-arm@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.2 + optional: true + + '@img/sharp-linux-ppc64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.2 + optional: true + + '@img/sharp-linux-riscv64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.2 + optional: true + + '@img/sharp-linux-s390x@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.2 + optional: true + + '@img/sharp-linux-x64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.2 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + optional: true + + '@img/sharp-wasm32@0.35.3': + dependencies: + '@emnapi/runtime': 1.11.1 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.3': + dependencies: + '@img/sharp-wasm32': 0.35.3 optional: true - '@esbuild/win32-x64@0.27.4': + '@img/sharp-win32-arm64@0.35.3': optional: true - '@exodus/bytes@1.15.0': {} - - '@floating-ui/core@1.7.5': - dependencies: - '@floating-ui/utils': 0.2.11 - - '@floating-ui/dom@1.7.6': - dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 + '@img/sharp-win32-ia32@0.35.3': + optional: true - '@floating-ui/utils@0.2.11': {} + '@img/sharp-win32-x64@0.35.3': + optional: true '@inquirer/external-editor@1.0.3(@types/node@25.5.0)': dependencies: @@ -4128,6 +4882,13 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4206,6 +4967,8 @@ snapshots: '@oxc-project/types@0.139.0': {} + '@oxc-project/types@0.140.0': {} + '@oxfmt/binding-android-arm-eabi@0.36.0': optional: true @@ -4296,7 +5059,7 @@ snapshots: '@parcel/watcher-wasm@2.5.6': dependencies: is-glob: 4.0.3 - picomatch: 4.0.3 + picomatch: 4.0.5 '@parcel/watcher-win32-arm64@2.5.6': optional: true @@ -4312,7 +5075,7 @@ snapshots: detect-libc: 2.1.2 is-glob: 4.0.3 node-addon-api: 7.1.1 - picomatch: 4.0.3 + picomatch: 4.0.5 optionalDependencies: '@parcel/watcher-android-arm64': 2.5.6 '@parcel/watcher-darwin-arm64': 2.5.6 @@ -4336,42 +5099,82 @@ snapshots: '@popperjs/core@2.11.8': {} + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + '@rolldown/binding-android-arm64@1.1.5': optional: true + '@rolldown/binding-android-arm64@1.2.0': + optional: true + '@rolldown/binding-darwin-arm64@1.1.5': optional: true + '@rolldown/binding-darwin-arm64@1.2.0': + optional: true + '@rolldown/binding-darwin-x64@1.1.5': optional: true + '@rolldown/binding-darwin-x64@1.2.0': + optional: true + '@rolldown/binding-freebsd-x64@1.1.5': optional: true + '@rolldown/binding-freebsd-x64@1.2.0': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.2.0': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.1.5': optional: true + '@rolldown/binding-linux-arm64-gnu@1.2.0': + optional: true + '@rolldown/binding-linux-arm64-musl@1.1.5': optional: true + '@rolldown/binding-linux-arm64-musl@1.2.0': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.1.5': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.2.0': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.1.5': optional: true + '@rolldown/binding-linux-s390x-gnu@1.2.0': + optional: true + '@rolldown/binding-linux-x64-gnu@1.1.5': optional: true + '@rolldown/binding-linux-x64-gnu@1.2.0': + optional: true + '@rolldown/binding-linux-x64-musl@1.1.5': optional: true + '@rolldown/binding-linux-x64-musl@1.2.0': + optional: true + '@rolldown/binding-openharmony-arm64@1.1.5': optional: true + '@rolldown/binding-openharmony-arm64@1.2.0': + optional: true + '@rolldown/binding-wasm32-wasi@1.1.5': dependencies: '@emnapi/core': 1.11.1 @@ -4379,12 +5182,25 @@ snapshots: '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true + '@rolldown/binding-wasm32-wasi@1.2.0': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + '@rolldown/binding-win32-arm64-msvc@1.1.5': optional: true + '@rolldown/binding-win32-arm64-msvc@1.2.0': + optional: true + '@rolldown/binding-win32-x64-msvc@1.1.5': optional: true + '@rolldown/binding-win32-x64-msvc@1.2.0': + optional: true + '@rolldown/pluginutils@1.0.1': {} '@shikijs/core@1.29.2': @@ -4603,6 +5419,19 @@ snapshots: dependencies: '@testing-library/dom': 10.4.1 + '@tsdown/css@0.22.12(jiti@2.7.0)(postcss@8.5.19)(tsdown@0.22.12)': + dependencies: + lightningcss: 1.32.0 + postcss-load-config: 6.0.1(jiti@2.7.0)(postcss@8.5.19) + rolldown: 1.2.0 + tsdown: 0.22.12(@tsdown/css@0.22.12)(typescript@7.0.2) + optionalDependencies: + postcss: 8.5.19 + transitivePeerDependencies: + - jiti + - tsx + - yaml + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -4739,6 +5568,20 @@ snapshots: - utf-8-validate - vite + '@vitest/browser-playwright@4.1.0(playwright@1.58.2)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0))(vitest@4.1.0)': + dependencies: + '@vitest/browser': 4.1.0(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0))(vitest@4.1.0) + '@vitest/mocker': 4.1.0(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + playwright: 1.58.2 + tinyrainbow: 3.1.0 + vitest: 4.1.0(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(@vitest/ui@4.1.0)(jsdom@28.1.0)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + '@vitest/browser@4.1.0(vite@8.1.4(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0))(vitest@4.1.0)': dependencies: '@blazediff/core': 1.9.1 @@ -4756,6 +5599,24 @@ snapshots: - utf-8-validate - vite + '@vitest/browser@4.1.0(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0))(vitest@4.1.0)': + dependencies: + '@blazediff/core': 1.9.1 + '@vitest/mocker': 4.1.0(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + '@vitest/utils': 4.1.0 + magic-string: 0.30.21 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.0 + vitest: 4.1.0(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(@vitest/ui@4.1.0)(jsdom@28.1.0)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + '@vitest/expect@4.1.0': dependencies: '@standard-schema/spec': 1.1.0 @@ -4773,6 +5634,14 @@ snapshots: optionalDependencies: vite: 8.1.4(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0) + '@vitest/mocker@4.1.0(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0))': + dependencies: + '@vitest/spy': 4.1.0 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0) + '@vitest/pretty-format@4.1.0': dependencies: tinyrainbow: 3.1.0 @@ -4798,9 +5667,9 @@ snapshots: flatted: 3.4.0 pathe: 2.0.3 sirv: 3.0.2 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vitest: 4.1.0(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(@vitest/ui@4.1.0)(jsdom@28.1.0)(vite@8.1.4(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + vitest: 4.1.0(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(@vitest/ui@4.1.0)(jsdom@28.1.0)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) '@vitest/utils@4.1.0': dependencies: @@ -4808,6 +5677,78 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@yuku-codegen/binding-darwin-arm64@0.6.12': + optional: true + + '@yuku-codegen/binding-darwin-x64@0.6.12': + optional: true + + '@yuku-codegen/binding-freebsd-x64@0.6.12': + optional: true + + '@yuku-codegen/binding-linux-arm-gnu@0.6.12': + optional: true + + '@yuku-codegen/binding-linux-arm-musl@0.6.12': + optional: true + + '@yuku-codegen/binding-linux-arm64-gnu@0.6.12': + optional: true + + '@yuku-codegen/binding-linux-arm64-musl@0.6.12': + optional: true + + '@yuku-codegen/binding-linux-x64-gnu@0.6.12': + optional: true + + '@yuku-codegen/binding-linux-x64-musl@0.6.12': + optional: true + + '@yuku-codegen/binding-win32-arm64@0.6.12': + optional: true + + '@yuku-codegen/binding-win32-x64@0.6.12': + optional: true + + '@yuku-parser/binding-darwin-arm64@0.6.12': + optional: true + + '@yuku-parser/binding-darwin-x64@0.6.12': + optional: true + + '@yuku-parser/binding-freebsd-x64@0.6.12': + optional: true + + '@yuku-parser/binding-linux-arm-gnu@0.6.12': + optional: true + + '@yuku-parser/binding-linux-arm-musl@0.6.12': + optional: true + + '@yuku-parser/binding-linux-arm64-gnu@0.6.12': + optional: true + + '@yuku-parser/binding-linux-arm64-musl@0.6.12': + optional: true + + '@yuku-parser/binding-linux-x64-gnu@0.6.12': + optional: true + + '@yuku-parser/binding-linux-x64-musl@0.6.12': + optional: true + + '@yuku-parser/binding-win32-arm64@0.6.12': + optional: true + + '@yuku-parser/binding-win32-x64@0.6.12': + optional: true + + '@yuku-toolchain/types@0.5.43': {} + + '@yuku-toolchain/types@0.6.11': {} + + '@yuku-toolchain/types@0.6.8': {} + acorn@8.16.0: {} agent-base@7.1.4: {} @@ -4818,6 +5759,8 @@ snapshots: ansi-styles@5.2.0: {} + ansis@4.3.1: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -4895,6 +5838,8 @@ snapshots: buffer-from@1.1.2: optional: true + cac@7.0.0: {} + camelcase-css@2.0.1: {} caniuse-lite@1.0.30001778: {} @@ -5020,6 +5965,8 @@ snapshots: defu@6.1.4: {} + defu@6.1.7: {} + denque@2.1.0: optional: true @@ -5050,10 +5997,14 @@ snapshots: dotenv@17.3.1: optional: true + dts-resolver@3.0.0: {} + electron-to-chromium@1.5.313: {} emoji-regex-xs@1.0.0: {} + empathic@2.0.1: {} + enhanced-resolve@5.21.6: dependencies: graceful-fs: 4.2.11 @@ -5107,6 +6058,7 @@ snapshots: '@esbuild/win32-arm64': 0.27.4 '@esbuild/win32-ia32': 0.27.4 '@esbuild/win32-x64': 0.27.4 + optional: true escalade@3.2.0: {} @@ -5195,11 +6147,15 @@ snapshots: get-stream@8.0.1: {} + get-tsconfig@5.0.0-beta.5: + dependencies: + resolve-pkg-maps: 1.0.0 + giget@2.0.0: dependencies: citty: 0.1.6 consola: 3.4.2 - defu: 6.1.4 + defu: 6.1.7 node-fetch-native: 1.6.7 nypm: 0.6.5 pathe: 2.0.3 @@ -5316,6 +6272,8 @@ snapshots: ignore@5.3.2: {} + import-without-cache@0.4.0: {} + indent-string@4.0.0: {} ioredis@5.10.0: @@ -5734,7 +6692,7 @@ snapshots: dependencies: citty: 0.2.1 pathe: 2.0.3 - tinyexec: 1.0.4 + tinyexec: 1.2.4 optional: true object-assign@4.1.1: {} @@ -5743,6 +6701,8 @@ snapshots: obug@2.1.1: {} + obug@2.1.4: {} + ocache@0.1.5: dependencies: ohash: 2.0.11 @@ -5911,6 +6871,13 @@ snapshots: jiti: 1.21.7 postcss: 8.5.8 + postcss-load-config@6.0.1(jiti@2.7.0)(postcss@8.5.19): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 2.7.0 + postcss: 8.5.19 + postcss-nested@6.2.0(postcss@8.5.8): dependencies: postcss: 8.5.8 @@ -5954,6 +6921,8 @@ snapshots: quansync@0.2.11: {} + quansync@1.0.0: {} + queue-microtask@1.2.3: {} radix3@1.1.2: {} @@ -6006,6 +6975,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.11: dependencies: is-core-module: 2.16.1 @@ -6014,6 +6985,20 @@ snapshots: reusify@1.1.0: {} + rolldown-plugin-dts@0.27.11(rolldown@1.2.0)(typescript@7.0.2): + dependencies: + dts-resolver: 3.0.0 + get-tsconfig: 5.0.0-beta.5 + obug: 2.1.4 + rolldown: 1.2.0 + yuku-ast: 0.1.7 + yuku-codegen: 0.6.12 + yuku-parser: 0.6.12 + optionalDependencies: + typescript: 7.0.2 + transitivePeerDependencies: + - oxc-resolver + rolldown@1.1.5: dependencies: '@oxc-project/types': 0.139.0 @@ -6035,6 +7020,27 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5 + rolldown@1.2.0: + dependencies: + '@oxc-project/types': 0.140.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.2.0 + '@rolldown/binding-darwin-arm64': 1.2.0 + '@rolldown/binding-darwin-x64': 1.2.0 + '@rolldown/binding-freebsd-x64': 1.2.0 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.0 + '@rolldown/binding-linux-arm64-gnu': 1.2.0 + '@rolldown/binding-linux-arm64-musl': 1.2.0 + '@rolldown/binding-linux-ppc64-gnu': 1.2.0 + '@rolldown/binding-linux-s390x-gnu': 1.2.0 + '@rolldown/binding-linux-x64-gnu': 1.2.0 + '@rolldown/binding-linux-x64-musl': 1.2.0 + '@rolldown/binding-openharmony-arm64': 1.2.0 + '@rolldown/binding-wasm32-wasi': 1.2.0 + '@rolldown/binding-win32-arm64-msvc': 1.2.0 + '@rolldown/binding-win32-x64-msvc': 1.2.0 + rou3@0.8.1: {} rou3@0.9.1: {} @@ -6053,6 +7059,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.5: {} + seroval-plugins@1.5.1(seroval@1.5.1): dependencies: seroval: 1.5.1 @@ -6065,6 +7073,39 @@ snapshots: seroval@1.5.4: {} + sharp@0.35.3(@types/node@25.5.0): + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.3 + '@img/sharp-darwin-x64': 0.35.3 + '@img/sharp-freebsd-wasm32': 0.35.3 + '@img/sharp-libvips-darwin-arm64': 1.3.2 + '@img/sharp-libvips-darwin-x64': 1.3.2 + '@img/sharp-libvips-linux-arm': 1.3.2 + '@img/sharp-libvips-linux-arm64': 1.3.2 + '@img/sharp-libvips-linux-ppc64': 1.3.2 + '@img/sharp-libvips-linux-riscv64': 1.3.2 + '@img/sharp-libvips-linux-s390x': 1.3.2 + '@img/sharp-libvips-linux-x64': 1.3.2 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.2 + '@img/sharp-libvips-linuxmusl-x64': 1.3.2 + '@img/sharp-linux-arm': 0.35.3 + '@img/sharp-linux-arm64': 0.35.3 + '@img/sharp-linux-ppc64': 0.35.3 + '@img/sharp-linux-riscv64': 0.35.3 + '@img/sharp-linux-s390x': 0.35.3 + '@img/sharp-linux-x64': 0.35.3 + '@img/sharp-linuxmusl-arm64': 0.35.3 + '@img/sharp-linuxmusl-x64': 0.35.3 + '@img/sharp-webcontainers-wasm32': 0.35.3 + '@img/sharp-win32-arm64': 0.35.3 + '@img/sharp-win32-ia32': 0.35.3 + '@img/sharp-win32-x64': 0.35.3 + '@types/node': 25.5.0 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -6198,7 +7239,7 @@ snapshots: lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.7 - tinyglobby: 0.2.15 + tinyglobby: 0.2.17 ts-interface-checker: 0.1.13 supports-preserve-symlinks-flag@1.0.0: {} @@ -6272,6 +7313,8 @@ snapshots: tinyexec@1.0.4: {} + tinyexec@1.2.4: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -6310,10 +7353,38 @@ snapshots: dependencies: punycode: 2.3.1 + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} ts-interface-checker@0.1.13: {} + tsdown@0.22.12(@tsdown/css@0.22.12)(typescript@7.0.2): + dependencies: + ansis: 4.3.1 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.1 + hookable: 6.1.1 + import-without-cache: 0.4.0 + obug: 2.1.4 + picomatch: 4.0.5 + rolldown: 1.2.0 + rolldown-plugin-dts: 0.27.11(rolldown@1.2.0)(typescript@7.0.2) + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + verkit: 0.1.0 + optionalDependencies: + '@tsdown/css': 0.22.12(jiti@2.7.0)(postcss@8.5.19)(tsdown@0.22.12) + typescript: 7.0.2 + transitivePeerDependencies: + - '@typescript/native-preview' + - '@volar/typescript' + - oxc-resolver + - vue-tsc + tslib@2.8.1: {} typescript@7.0.2: @@ -6341,6 +7412,11 @@ snapshots: ufo@1.6.3: {} + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + uncrypto@0.1.3: {} undici-types@7.18.2: {} @@ -6415,6 +7491,8 @@ snapshots: util-deprecate@1.0.2: {} + verkit@0.1.0: {} + vfile-message@4.0.3: dependencies: '@types/unist': 3.0.3 @@ -6440,6 +7518,21 @@ snapshots: transitivePeerDependencies: - supports-color + vite-plugin-solid@2.11.11(@testing-library/jest-dom@6.9.1)(solid-js@1.9.11)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)): + dependencies: + '@babel/core': 7.29.0 + '@types/babel__core': 7.20.5 + babel-preset-solid: 1.9.10(@babel/core@7.29.0)(solid-js@1.9.11) + merge-anything: 5.1.7 + solid-js: 1.9.11 + solid-refresh: 0.6.3(solid-js@1.9.11) + vite: 8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0) + vitefu: 1.1.2(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + optionalDependencies: + '@testing-library/jest-dom': 6.9.1 + transitivePeerDependencies: + - supports-color + vite@8.1.4(@types/node@25.5.0)(esbuild@0.27.4)(jiti@1.21.7)(terser@5.46.0): dependencies: lightningcss: 1.32.0 @@ -6468,10 +7561,28 @@ snapshots: jiti: 2.7.0 terser: 5.46.0 + vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.5 + postcss: 8.5.19 + rolldown: 1.1.5 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 25.5.0 + esbuild: 0.27.4 + fsevents: 2.3.3 + jiti: 2.7.0 + terser: 5.46.0 + vitefu@1.1.2(vite@8.1.4(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)): optionalDependencies: vite: 8.1.4(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0) + vitefu@1.1.2(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)): + optionalDependencies: + vite: 8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0) + vitest@4.1.0(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(@vitest/ui@4.1.0)(jsdom@28.1.0)(vite@8.1.4(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)): dependencies: '@vitest/expect': 4.1.0 @@ -6502,6 +7613,36 @@ snapshots: transitivePeerDependencies: - msw + vitest@4.1.0(@types/node@25.5.0)(@vitest/browser-playwright@4.1.0)(@vitest/ui@4.1.0)(jsdom@28.1.0)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)): + dependencies: + '@vitest/expect': 4.1.0 + '@vitest/mocker': 4.1.0(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0)) + '@vitest/pretty-format': 4.1.0 + '@vitest/runner': 4.1.0 + '@vitest/snapshot': 4.1.0 + '@vitest/spy': 4.1.0 + '@vitest/utils': 4.1.0 + 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 + tinybench: 2.9.0 + tinyexec: 1.0.4 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.5.0 + '@vitest/browser-playwright': 4.1.0(playwright@1.58.2)(vite@8.1.5(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(terser@5.46.0))(vitest@4.1.0) + '@vitest/ui': 4.1.0(vitest@4.1.0) + jsdom: 28.1.0 + transitivePeerDependencies: + - msw + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 @@ -6535,4 +7676,45 @@ snapshots: yallist@3.1.1: {} + yuku-ast@0.1.7: + dependencies: + '@yuku-toolchain/types': 0.5.43 + + yuku-ast@0.6.11: + dependencies: + '@yuku-toolchain/types': 0.6.8 + + yuku-codegen@0.6.12: + dependencies: + '@yuku-toolchain/types': 0.6.11 + optionalDependencies: + '@yuku-codegen/binding-darwin-arm64': 0.6.12 + '@yuku-codegen/binding-darwin-x64': 0.6.12 + '@yuku-codegen/binding-freebsd-x64': 0.6.12 + '@yuku-codegen/binding-linux-arm-gnu': 0.6.12 + '@yuku-codegen/binding-linux-arm-musl': 0.6.12 + '@yuku-codegen/binding-linux-arm64-gnu': 0.6.12 + '@yuku-codegen/binding-linux-arm64-musl': 0.6.12 + '@yuku-codegen/binding-linux-x64-gnu': 0.6.12 + '@yuku-codegen/binding-linux-x64-musl': 0.6.12 + '@yuku-codegen/binding-win32-arm64': 0.6.12 + '@yuku-codegen/binding-win32-x64': 0.6.12 + + yuku-parser@0.6.12: + dependencies: + '@yuku-toolchain/types': 0.6.11 + yuku-ast: 0.6.11 + optionalDependencies: + '@yuku-parser/binding-darwin-arm64': 0.6.12 + '@yuku-parser/binding-darwin-x64': 0.6.12 + '@yuku-parser/binding-freebsd-x64': 0.6.12 + '@yuku-parser/binding-linux-arm-gnu': 0.6.12 + '@yuku-parser/binding-linux-arm-musl': 0.6.12 + '@yuku-parser/binding-linux-arm64-gnu': 0.6.12 + '@yuku-parser/binding-linux-arm64-musl': 0.6.12 + '@yuku-parser/binding-linux-x64-gnu': 0.6.12 + '@yuku-parser/binding-linux-x64-musl': 0.6.12 + '@yuku-parser/binding-win32-arm64': 0.6.12 + '@yuku-parser/binding-win32-x64': 0.6.12 + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e93b431c2..772bf3a3c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,7 +16,10 @@ ignoredBuiltDependencies: - prisma minimumReleaseAgeExclude: + - '@tsdown/css@0.22.12' - srvx@0.12.0 + - tsdown@0.22.12 + - verkit@0.1.0 onlyBuiltDependencies: - '@parcel/watcher'