├── version.txt ├── data ├── paths.json ├── subdomains.json └── domains.json ├── .gitignore ├── demo ├── .gitignore ├── src │ ├── assets │ │ ├── icon.png │ │ └── favicon.ico │ ├── index.jsx │ ├── sw.js │ ├── manifest.json │ ├── index.html │ ├── template.html │ ├── examples.json │ ├── style.css │ └── App.jsx ├── netlify.toml ├── package.json └── yarn.lock ├── mod.ts ├── deno.jsonc ├── scripts ├── example.ts └── build_npm.ts ├── .github └── workflows │ ├── release-please.yml │ ├── test.yml │ └── npm-publish.yml ├── src ├── utils.test.ts ├── detect.ts ├── transformers │ ├── bunny.ts │ ├── wordpress.ts │ ├── contentful.ts │ ├── builder.ts │ ├── wordpress.test.ts │ ├── cloudflare.test.ts │ ├── shopify.test.ts │ ├── imgix.ts │ ├── shopify.ts │ ├── contentful.test.ts │ ├── builder.test.ts │ ├── storyblok.test.ts │ ├── shopify.fixtures.json │ ├── cloudflare.ts │ ├── imgix.test.ts │ ├── storyblok.ts │ ├── cloudinary.ts │ └── cloudinary.test.ts ├── utils.ts ├── types.ts ├── transform.ts └── parse.ts ├── .vscode └── settings.json ├── CHANGELOG.md ├── example └── next-image.ts ├── README.md └── deno.lock /version.txt: -------------------------------------------------------------------------------- 1 | 2.1.0 2 | -------------------------------------------------------------------------------- /data/paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "/cdn-cgi/image/": "cloudflare" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm 2 | # Local Netlify folder 3 | .netlify 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /build 3 | /*.log 4 | .parcel-cache 5 | /dist -------------------------------------------------------------------------------- /demo/src/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriticalflare/unpic/main/demo/src/assets/icon.png -------------------------------------------------------------------------------- /demo/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kriticalflare/unpic/main/demo/src/assets/favicon.ico -------------------------------------------------------------------------------- /demo/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "yarn build" 3 | publish = "dist" 4 | ignore = "git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF .." 5 | -------------------------------------------------------------------------------- /demo/src/index.jsx: -------------------------------------------------------------------------------- 1 | import { h, render } from "preact"; 2 | import App from "./App"; 3 | 4 | render(, document.getElementById("root")); 5 | 6 | -------------------------------------------------------------------------------- /demo/src/sw.js: -------------------------------------------------------------------------------- 1 | import { getFiles, setupPrecaching, setupRouting } from 'preact-cli/sw/'; 2 | 3 | setupRouting(); 4 | setupPrecaching(getFiles()); 5 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/types.ts"; 2 | export * from "./src/transform.ts"; 3 | export * from "./src/detect.ts"; 4 | export * from "./src/parse.ts"; 5 | -------------------------------------------------------------------------------- /data/subdomains.json: -------------------------------------------------------------------------------- 1 | { 2 | "imgix.net": "imgix", 3 | "files.wordpress.com": "wordpress", 4 | "b-cdn.net": "bunny", 5 | "storyblok.com": "storyblok" 6 | } 7 | -------------------------------------------------------------------------------- /deno.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "build:npm": "deno run --allow-all scripts/build_npm.ts" 4 | }, 5 | "compilerOptions": { 6 | "jsx": "react-jsx", 7 | "jsxImportSource": "https://esm.sh/preact@10.11.2" 8 | } 9 | } -------------------------------------------------------------------------------- /scripts/example.ts: -------------------------------------------------------------------------------- 1 | import { transformUrl } from "../mod.ts"; 2 | 3 | const url = 4 | "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg"; 5 | 6 | console.log(transformUrl({ 7 | url, 8 | width: 800, 9 | height: 600, 10 | })); 11 | -------------------------------------------------------------------------------- /data/domains.json: -------------------------------------------------------------------------------- 1 | { 2 | "res.cloudinary.com": "cloudinary", 3 | "images.ctfassets.net": "contentful", 4 | "cdn.builder.io": "builder.io", 5 | "images.prismic.io": "imgix", 6 | "www.datocms-assets.com": "imgix", 7 | "cdn.sanity.io": "imgix", 8 | "images.unsplash.com": "imgix", 9 | "cdn.shopify.com": "shopify" 10 | } 11 | -------------------------------------------------------------------------------- /demo/src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "short_name": "demo", 4 | "start_url": "/", 5 | "display": "standalone", 6 | "orientation": "portrait", 7 | "background_color": "#fff", 8 | "theme_color": "#673ab8", 9 | "icons": [ 10 | { 11 | "src": "/assets/icon.png", 12 | "type": "image/png", 13 | "sizes": "512x512" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Unpic 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: release-please 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | contents: write 11 | pull-requests: write 12 | steps: 13 | - uses: google-github-actions/release-please-action@v3 14 | with: 15 | release-type: simple 16 | package-name: unpic 17 | ssh-key: "${{ secrets.COMMIT_KEY }}" -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Deno test 2 | on: 3 | pull_request: 4 | push: 5 | branches: [main] 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | - name: Setup Deno 13 | uses: denoland/setup-deno@v1 14 | with: 15 | deno-version: vx.x.x 16 | - name: Typecheck 17 | run: deno check src/**/*.ts 18 | - name: Test 19 | run: deno test src/ -------------------------------------------------------------------------------- /demo/src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% preact.title %> 6 | 7 | 8 | 9 | 10 | <% preact.headEnd %> 11 | 12 | 13 | <% preact.bodyEnd %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "demo", 4 | "version": "0.0.0", 5 | "license": "MIT", 6 | "scripts": { 7 | "build": "parcel build src/index.html", 8 | "dev": "parcel src/index.html", 9 | "lint": "eslint src" 10 | }, 11 | "eslintConfig": { 12 | "extends": "preact" 13 | }, 14 | "eslintIgnore": [ 15 | "build/*" 16 | ], 17 | "devDependencies": { 18 | "parcel": "^2.8.3" 19 | }, 20 | "dependencies": { 21 | "@preact/signals": "^1.1.3", 22 | "preact": "^10.1.0", 23 | "preact-render-to-string": "^5.1.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils.test.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 3 | import { roundIfNumeric } from "./utils.ts"; 4 | 5 | Deno.test("roundIfNumeric", () => { 6 | assertEquals(roundIfNumeric(1), 1); 7 | assertEquals(roundIfNumeric(1.1), 1); 8 | assertEquals(roundIfNumeric(1.6), 2); 9 | assertEquals(roundIfNumeric("1"), 1); 10 | assertEquals(roundIfNumeric("1.1"), 1); 11 | assertEquals(roundIfNumeric("1.6"), 2); 12 | assertEquals(roundIfNumeric("foo"), "foo"); 13 | assertEquals(roundIfNumeric(""), ""); 14 | assertEquals(roundIfNumeric("0"), 0); 15 | assertEquals(roundIfNumeric(null as any), null); 16 | assertEquals(roundIfNumeric(0), 0); 17 | assertEquals(roundIfNumeric(undefined as any), undefined); 18 | }); 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.unstable": true, 4 | "deno.enablePaths": [ 5 | "src", 6 | "scripts", 7 | "mod.ts", 8 | "deps.ts", 9 | "*/*.json", 10 | "example" 11 | ], 12 | "editor.formatOnSave": true, 13 | "[typescript]": { 14 | "editor.defaultFormatter": "denoland.vscode-deno" 15 | }, 16 | "[markdown]": { 17 | "editor.defaultFormatter": "denoland.vscode-deno" 18 | }, 19 | "[json]": { 20 | "editor.defaultFormatter": "denoland.vscode-deno" 21 | }, 22 | "[typescriptreact]": { 23 | "editor.defaultFormatter": "denoland.vscode-deno" 24 | }, 25 | "[javascript]": { 26 | "editor.defaultFormatter": "denoland.vscode-deno" 27 | }, 28 | "[javascriptreact]": { 29 | "editor.defaultFormatter": "denoland.vscode-deno" 30 | }, 31 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [2.1.0](https://github.com/ascorbic/unpic/compare/2.0.2...v2.1.0) (2023-02-24) 4 | 5 | 6 | ### Features 7 | 8 | * **builder:** add Builder.io ([#13](https://github.com/ascorbic/unpic/issues/13)) ([fb31a94](https://github.com/ascorbic/unpic/commit/fb31a94edf9e08a00f8f72258b38123b9c4d27ad)) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * handle workflow ([#10](https://github.com/ascorbic/unpic/issues/10)) ([d59485f](https://github.com/ascorbic/unpic/commit/d59485f22decb0cd7146d5443c438d41f247747e)) 14 | * permissions on rp workflow ([6e1dcdf](https://github.com/ascorbic/unpic/commit/6e1dcdfc1bf490738f5bab292ed48e096f8504a2)) 15 | * setup releasing ([#9](https://github.com/ascorbic/unpic/issues/9)) ([2a609a7](https://github.com/ascorbic/unpic/commit/2a609a7b7f09ed887c0ca2bd8e90dc82b05a787b)) 16 | -------------------------------------------------------------------------------- /scripts/build_npm.ts: -------------------------------------------------------------------------------- 1 | import { build, emptyDir } from "https://deno.land/x/dnt@0.22.0/mod.ts"; 2 | 3 | await emptyDir("./npm"); 4 | 5 | await build({ 6 | entryPoints: ["./mod.ts"], 7 | outDir: "./npm", 8 | shims: { 9 | deno: { 10 | test: "dev", 11 | }, 12 | }, 13 | rootTestDir: "./src", 14 | package: { 15 | // package.json properties 16 | name: "unpic", 17 | version: Deno.args[0]?.replace(/^v/, ""), 18 | description: "Universal image CDN translator", 19 | license: "MIT", 20 | repository: { 21 | type: "git", 22 | url: "git+https://github.com/ascorbic/unpic.git", 23 | }, 24 | bugs: { 25 | url: "https://github.com/ascorbic/unpic/issues", 26 | }, 27 | }, 28 | }); 29 | 30 | // post build steps 31 | // Deno.copyFileSync("LICENSE", "npm/LICENSE"); 32 | Deno.copyFileSync("README.md", "npm/README.md"); 33 | -------------------------------------------------------------------------------- /src/detect.ts: -------------------------------------------------------------------------------- 1 | import domains from "../data/domains.json" assert { type: "json" }; 2 | import subdomains from "../data/subdomains.json" assert { type: "json" }; 3 | import paths from "../data/paths.json" assert { type: "json" }; 4 | import { ImageCdn } from "./types.ts"; 5 | 6 | const cdnDomains = new Map(Object.entries(domains)); 7 | const cdnSubdomains = Object.entries(subdomains); 8 | 9 | export function getImageCdnForUrl(url: string | URL): ImageCdn | false { 10 | const { hostname, pathname } = new URL(url); 11 | if (cdnDomains.has(hostname)) { 12 | return cdnDomains.get(hostname) as ImageCdn; 13 | } 14 | for (const [subdomain, cdn] of cdnSubdomains) { 15 | if (hostname.endsWith(`.${subdomain}`)) { 16 | return cdn as ImageCdn; 17 | } 18 | } 19 | for (const [prefix, cdn] of Object.entries(paths)) { 20 | if (pathname.startsWith(prefix)) { 21 | return cdn as ImageCdn; 22 | } 23 | } 24 | return false; 25 | } 26 | -------------------------------------------------------------------------------- /src/transformers/bunny.ts: -------------------------------------------------------------------------------- 1 | import { UrlParser, UrlTransformer } from "../types.ts"; 2 | import { getNumericParam, setParamIfDefined } from "../utils.ts"; 3 | 4 | export const parse: UrlParser<{ fit?: string }> = (url) => { 5 | const parsedUrl = new URL(url); 6 | 7 | const width = getNumericParam(parsedUrl, "width"); 8 | const height = getNumericParam(parsedUrl, "height"); 9 | const params: Record = {}; 10 | parsedUrl.searchParams.forEach((value, key) => { 11 | params[key] = value; 12 | }); 13 | parsedUrl.search = ""; 14 | return { 15 | width, 16 | height, 17 | base: parsedUrl.toString(), 18 | params, 19 | cdn: "bunny", 20 | }; 21 | }; 22 | 23 | export const transform: UrlTransformer = ( 24 | { url: originalUrl, width, height }, 25 | ) => { 26 | const url = new URL(originalUrl); 27 | setParamIfDefined(url, "width", width, true, true); 28 | setParamIfDefined(url, "height", height, true, true); 29 | return url; 30 | }; 31 | -------------------------------------------------------------------------------- /src/transformers/wordpress.ts: -------------------------------------------------------------------------------- 1 | import { UrlParser, UrlTransformer } from "../types.ts"; 2 | import { 3 | getNumericParam, 4 | setParamIfDefined, 5 | setParamIfUndefined, 6 | } from "../utils.ts"; 7 | 8 | export const transform: UrlTransformer = ( 9 | { url: originalUrl, width, height }, 10 | ) => { 11 | const url = new URL(originalUrl); 12 | setParamIfDefined(url, "w", width, true, true); 13 | setParamIfDefined(url, "h", height, true, true); 14 | setParamIfUndefined(url, "crop", "1"); 15 | return url; 16 | }; 17 | 18 | export const parse: UrlParser<{ crop?: boolean }> = ( 19 | url, 20 | ) => { 21 | const parsed = new URL(url); 22 | const width = getNumericParam(parsed, "w"); 23 | const height = getNumericParam(parsed, "h"); 24 | const crop = parsed.searchParams.get("crop") === "1"; 25 | parsed.search = ""; 26 | return { 27 | base: parsed.toString(), 28 | width, 29 | height, 30 | params: { crop }, 31 | cdn: "wordpress", 32 | }; 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export const roundIfNumeric = (value: string | number) => { 2 | if (!value) { 3 | return value; 4 | } 5 | const num = Number(value); 6 | return isNaN(num) ? value : Math.round(num); 7 | }; 8 | 9 | export const setParamIfDefined = ( 10 | url: URL, 11 | key: string, 12 | value?: string | number, 13 | deleteExisting?: boolean, 14 | roundValue?: boolean, 15 | ) => { 16 | if (value) { 17 | if (roundValue) { 18 | value = roundIfNumeric(value); 19 | } 20 | url.searchParams.set(key, value.toString()); 21 | } else if (deleteExisting) { 22 | url.searchParams.delete(key); 23 | } 24 | }; 25 | 26 | export const setParamIfUndefined = ( 27 | url: URL, 28 | key: string, 29 | value: string | number, 30 | ) => { 31 | if (!url.searchParams.has(key)) { 32 | url.searchParams.set(key, value.toString()); 33 | } 34 | }; 35 | 36 | export const getNumericParam = (url: URL, key: string) => { 37 | const value = Number(url.searchParams.get(key)); 38 | return isNaN(value) ? undefined : value + 1; 39 | }; 40 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: NPM Publish 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | release: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: write 10 | pull-requests: write 11 | steps: 12 | - name: Setup Deno 13 | uses: denoland/setup-deno@v1 14 | with: 15 | deno-version: vx.x.x 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | - name: Test 19 | run: deno test src/ 20 | - name: Get tag version 21 | if: startsWith(github.ref, 'refs/tags/') 22 | id: get_tag_version 23 | run: echo ::set-output name=TAG_VERSION::${GITHUB_REF/refs\/tags\//} 24 | - name: dnt 25 | if: startsWith(github.ref, 'refs/tags/') 26 | run: deno run -A ./scripts/build_npm.ts ${{steps.get_tag_version.outputs.TAG_VERSION}} 27 | - uses: actions/setup-node@v3 28 | with: 29 | registry-url: 'https://registry.npmjs.org' 30 | - name: npm publish 31 | if: startsWith(github.ref, 'refs/tags/') 32 | env: 33 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 34 | run: cd npm && npm publish -------------------------------------------------------------------------------- /example/next-image.ts: -------------------------------------------------------------------------------- 1 | // This is a Netlify Edge Function to transform Next.js image URLs. If they are from a 2 | // supported image CDN, transform the URL to use the CDN. Otherwise, pass through. 3 | 4 | import { transformUrl } from "../mod.ts"; 5 | 6 | export const transformNextImageUrl = ( 7 | url: string | URL, 8 | ): URL | undefined => { 9 | // Extract the source image URL and any width/quality params 10 | const reqUrl = new URL(url); 11 | const width = reqUrl.searchParams.get("w"); 12 | const cdnUrl = reqUrl.searchParams.get("url"); 13 | if (!cdnUrl) { 14 | console.log("No url param"); 15 | return; 16 | } 17 | // Try to transform the image URL to a CDN URL 18 | return transformUrl({ 19 | url: cdnUrl, 20 | width: width ? parseInt(width) : undefined, 21 | }); 22 | }; 23 | 24 | // If the source image is from an image CDN, transform using the CDN 25 | export const handler = (request: Request) => { 26 | const url = transformNextImageUrl(request.url); 27 | if (!url) { 28 | // Ignore and pass through 29 | return; 30 | } 31 | return Response.redirect(url, 301); 32 | }; 33 | 34 | export const config = { 35 | path: "/_next/image", 36 | }; 37 | -------------------------------------------------------------------------------- /src/transformers/contentful.ts: -------------------------------------------------------------------------------- 1 | import { UrlParser, UrlTransformer } from "../types.ts"; 2 | import { 3 | getNumericParam, 4 | setParamIfDefined, 5 | setParamIfUndefined, 6 | } from "../utils.ts"; 7 | 8 | export const parse: UrlParser<{ fit?: string }> = (url) => { 9 | const parsedUrl = new URL(url); 10 | 11 | const fit = parsedUrl.searchParams.get("fit") || undefined; 12 | const width = getNumericParam(parsedUrl, "w"); 13 | const height = getNumericParam(parsedUrl, "h"); 14 | const quality = getNumericParam(parsedUrl, "q"); 15 | const format = parsedUrl.searchParams.get("fm") || undefined; 16 | parsedUrl.search = ""; 17 | return { 18 | width, 19 | height, 20 | format, 21 | base: parsedUrl.toString(), 22 | params: { fit, quality }, 23 | cdn: "contentful", 24 | }; 25 | }; 26 | 27 | export const transform: UrlTransformer = ( 28 | { url: originalUrl, width, height, format }, 29 | ) => { 30 | const url = new URL(originalUrl); 31 | setParamIfDefined(url, "w", width, true, true); 32 | setParamIfDefined(url, "h", height, true, true); 33 | setParamIfDefined(url, "fm", format); 34 | setParamIfUndefined(url, "fit", "fill"); 35 | return url; 36 | }; 37 | -------------------------------------------------------------------------------- /src/transformers/builder.ts: -------------------------------------------------------------------------------- 1 | import { UrlParser, UrlTransformer } from "../types.ts"; 2 | import { 3 | getNumericParam, 4 | setParamIfDefined, 5 | setParamIfUndefined, 6 | } from "../utils.ts"; 7 | 8 | export const parse: UrlParser<{ fit?: string; quality?: number }> = (url) => { 9 | const parsedUrl = new URL(url); 10 | 11 | const width = getNumericParam(parsedUrl, "width"); 12 | const height = getNumericParam(parsedUrl, "height"); 13 | const quality = getNumericParam(parsedUrl, "quality"); 14 | const format = parsedUrl.searchParams.get("format") || undefined; 15 | const fit = parsedUrl.searchParams.get("fit") || undefined; 16 | parsedUrl.search = ""; 17 | 18 | return { 19 | width, 20 | height, 21 | format, 22 | base: parsedUrl.toString(), 23 | params: { quality, fit }, 24 | cdn: "builder.io", 25 | }; 26 | }; 27 | 28 | export const transform: UrlTransformer = ( 29 | { url: originalUrl, width, height, format }, 30 | ) => { 31 | const url = new URL(originalUrl); 32 | setParamIfUndefined(url, "fit", "cover"); 33 | setParamIfDefined(url, "width", width, true, true); 34 | setParamIfDefined(url, "height", height, true, true); 35 | setParamIfDefined(url, "format", format); 36 | return url; 37 | }; 38 | -------------------------------------------------------------------------------- /src/transformers/wordpress.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 2 | import { transform } from "./wordpress.ts"; 3 | 4 | const img = "https://jetpackme.files.wordpress.com/2020/01/jetpack-cdn.png"; 5 | 6 | Deno.test("wordpress", async (t) => { 7 | await t.step("should format a URL", () => { 8 | const result = transform({ url: img, width: 200, height: 100 }); 9 | assertEquals( 10 | result?.toString(), 11 | "https://jetpackme.files.wordpress.com/2020/01/jetpack-cdn.png?w=200&h=100&crop=1", 12 | ); 13 | }); 14 | 15 | await t.step("should round non-numeric values", () => { 16 | const result = transform({ url: img, width: 200.6, height: 100.2 }); 17 | assertEquals( 18 | result?.toString(), 19 | "https://jetpackme.files.wordpress.com/2020/01/jetpack-cdn.png?w=201&h=100&crop=1", 20 | ); 21 | }); 22 | 23 | await t.step("should not change crop if set", () => { 24 | const url = new URL(img); 25 | url.searchParams.set("crop", "0"); 26 | const result = transform({ url, width: 200, height: 100 }); 27 | assertEquals( 28 | result?.toString(), 29 | "https://jetpackme.files.wordpress.com/2020/01/jetpack-cdn.png?crop=0&w=200&h=100", 30 | ); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/transformers/cloudflare.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 2 | import { ParsedUrl } from "../types.ts"; 3 | import { CloudflareParams, parse, transform } from "./cloudflare.ts"; 4 | 5 | const img = 6 | "https://assets.brevity.io/cdn-cgi/image/background=red,width=128,height=128,f=auto/uploads/generic/avatar-sample.jpeg" 7 | 8 | Deno.test("cloudflare parser", () => { 9 | const parsed = parse(img); 10 | const expected: ParsedUrl = { 11 | base: img, 12 | cdn: "cloudflare", 13 | format: "auto", 14 | width: 128, 15 | height: 128, 16 | params: { 17 | host: "assets.brevity.io", 18 | transformations: { 19 | background: "red", 20 | }, 21 | path: "uploads/generic/avatar-sample.jpeg", 22 | }, 23 | }; 24 | assertEquals(parsed, expected); 25 | }); 26 | 27 | Deno.test("cloudflare transformer", async (t) => { 28 | await t.step("transforms a URL", () => { 29 | const result = transform({ 30 | url: img, 31 | width: 100, 32 | height: 200, 33 | }); 34 | assertEquals( 35 | result?.toString(), 36 | "https://assets.brevity.io/cdn-cgi/image/background=red,width=100,height=200,f=auto/uploads/generic/avatar-sample.jpeg" 37 | ); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /demo/src/examples.json: -------------------------------------------------------------------------------- 1 | [ 2 | [ 3 | "Shopify", 4 | "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg" 5 | ], 6 | [ 7 | "Contentful", 8 | "https://images.ctfassets.net/yadj1kx9rmg0/wtrHxeu3zEoEce2MokCSi/cf6f68efdcf625fdc060607df0f3baef/quwowooybuqbl6ntboz3.jpg?fm=jpg" 9 | ], 10 | [ 11 | "Builder.io", 12 | "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?format=webp&fit=cover" 13 | ], 14 | [ 15 | "Cloudinary", 16 | "https://res.cloudinary.com/demo/image/upload/c_lfill,w_800,h_550,f_auto/dog.webp" 17 | ], 18 | [ 19 | "Imgix (Unsplash)", 20 | "https://images.unsplash.com/photo-1674255909399-9bcb2cab6489?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=996&q=80" 21 | ], 22 | [ 23 | "WordPress", 24 | "https://cultivatedemo.files.wordpress.com/2022/06/marisa-morton-c9xtptclntg-unsplash-1.jpg" 25 | ], 26 | [ 27 | "Bunny.net", 28 | "https://bunnyoptimizerdemo.b-cdn.net/bunny7.jpg?width=300" 29 | ], 30 | [ 31 | "Storyblok", 32 | "https://a.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg/m/-800x600/filters:round_corner(30,255,255,255)" 33 | ], 34 | [ 35 | "Cloudflare", 36 | "https://assets.brevity.io/cdn-cgi/image/background=red,width=128,height=128,f=auto/uploads/generic/avatar-sample.jpeg" 37 | ] 38 | ] 39 | -------------------------------------------------------------------------------- /src/transformers/shopify.test.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 3 | import { parse, transform } from "./shopify.ts"; 4 | import examples from "./shopify.fixtures.json" assert { type: "json" }; 5 | 6 | const img = 7 | "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage_medium_crop_top.webp?v=3"; 8 | 9 | Deno.test("shopify parser", async (t) => { 10 | for (const { original, ...example } of examples) { 11 | await t.step(original, () => { 12 | const { params, ...parsed } = parse(original) as any; 13 | // Convert null from JSON into undefined for assertEquals 14 | const expected = Object.fromEntries( 15 | Object.entries(example).map(([k, v]) => [k, v ?? undefined]), 16 | ); 17 | expected.cdn = "shopify"; 18 | const { crop, size } = params || {}; 19 | assertEquals({ crop, size, ...parsed }, expected); 20 | }); 21 | } 22 | }); 23 | 24 | Deno.test("shopify transformer", async (t) => { 25 | await t.step("transforms a URL", () => { 26 | const result = transform({ 27 | url: img, 28 | width: 100, 29 | height: 200, 30 | }); 31 | assertEquals( 32 | result?.toString(), 33 | "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.webp?v=3&width=100&height=200&crop=top", 34 | ); 35 | }); 36 | 37 | await t.step("rounds non-numeric params", () => { 38 | const result = transform({ 39 | url: img, 40 | width: 100.2, 41 | height: 200.6, 42 | }); 43 | assertEquals( 44 | result?.toString(), 45 | "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.webp?v=3&width=100&height=201&crop=top", 46 | ); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /src/transformers/imgix.ts: -------------------------------------------------------------------------------- 1 | import { UrlParser, UrlTransformer } from "../types.ts"; 2 | import { setParamIfDefined, setParamIfUndefined } from "../utils.ts"; 3 | 4 | export const parse: UrlParser = ( 5 | url, 6 | ) => { 7 | const parsed = new URL(url); 8 | const width = Number(parsed.searchParams.get("w")) || undefined; 9 | const height = Number(parsed.searchParams.get("h")) || undefined; 10 | const quality = Number(parsed.searchParams.get("q")) || undefined; 11 | const format = parsed.searchParams.get("fm") || undefined; 12 | const params: Record = {}; 13 | parsed.searchParams.forEach((value, key) => { 14 | params[key] = value; 15 | }); 16 | parsed.search = ""; 17 | return { 18 | base: parsed.toString(), 19 | width, 20 | height, 21 | quality, 22 | format, 23 | params, 24 | cdn: "imgix", 25 | }; 26 | }; 27 | 28 | export const transform: UrlTransformer = ( 29 | { url: originalUrl, width, height, format }, 30 | ) => { 31 | const url = new URL(originalUrl); 32 | setParamIfDefined(url, "w", width, true, true); 33 | setParamIfDefined(url, "h", height, true, true); 34 | setParamIfUndefined(url, "fit", "min"); 35 | 36 | if (format) { 37 | url.searchParams.set("fm", format); 38 | const fm = url.searchParams.get("auto"); 39 | if (fm === "format") { 40 | url.searchParams.delete("auto"); 41 | } else if (fm?.includes("format")) { 42 | url.searchParams.set( 43 | "auto", 44 | fm.split(",").filter((s) => s !== "format").join(","), 45 | ); 46 | } 47 | } else { 48 | url.searchParams.delete("fm"); 49 | if (!url.searchParams.get("auto")?.includes("format")) { 50 | url.searchParams.append("auto", "format"); 51 | } 52 | } 53 | return url; 54 | }; 55 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Options to transform an image URL 3 | */ 4 | export interface UrlTransformerOptions { 5 | /** The original URL of the image */ 6 | url: string | URL; 7 | /** The desired width of the image */ 8 | width?: number; 9 | /** The desired height of the image */ 10 | height?: number; 11 | /** The desired format of the image. Default is auto-detect */ 12 | format?: string; 13 | /** Specify a CDN rather than auto-detecting */ 14 | cdn?: ImageCdn; 15 | } 16 | 17 | export interface UrlGeneratorOptions> { 18 | base: string | URL; 19 | width?: number; 20 | height?: number; 21 | format?: string; 22 | params?: TParams; 23 | } 24 | 25 | export interface UrlGenerator> { 26 | (options: UrlGeneratorOptions): URL; 27 | } 28 | 29 | export interface ParsedUrl> { 30 | /** The URL of the image with no transforms */ 31 | base: string; 32 | /** The width of the image */ 33 | width?: number; 34 | /** The height of the image */ 35 | height?: number; 36 | /** The format of the image */ 37 | format?: string; 38 | /** Other CDN-specific parameters */ 39 | params?: TParams; 40 | cdn: ImageCdn; 41 | } 42 | /** 43 | * Parse an image URL into its components 44 | */ 45 | export interface UrlTransformer { 46 | (options: UrlTransformerOptions): URL | undefined; 47 | } 48 | 49 | export interface UrlParser< 50 | TParams = Record, 51 | > { 52 | (url: string | URL): ParsedUrl; 53 | } 54 | 55 | export type ImageCdn = 56 | | "contentful" 57 | | "builder.io" 58 | | "cloudinary" 59 | | "cloudflare" 60 | | "imgix" 61 | | "shopify" 62 | | "wordpress" 63 | | "bunny" 64 | | "storyblok"; 65 | 66 | export type SupportedImageCdn = ImageCdn; 67 | -------------------------------------------------------------------------------- /src/transformers/shopify.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UrlGenerator, 3 | UrlGeneratorOptions, 4 | UrlParser, 5 | UrlTransformer, 6 | } from "../types.ts"; 7 | import { setParamIfDefined } from "../utils.ts"; 8 | 9 | const shopifyRegex = 10 | /(.+?)(?:_(?:(pico|icon|thumb|small|compact|medium|large|grande|original|master)|(\d*)x(\d*)))?(?:_crop_([a-z]+))?(\.[a-zA-Z]+)(\.png|\.jpg|\.webp|\.avif)?$/; 11 | 12 | export const parse: UrlParser<{ crop?: string; size?: string }> = ( 13 | imageUrl, 14 | ) => { 15 | const url = new URL(imageUrl); 16 | const match = url.pathname.match(shopifyRegex); 17 | if (!match) { 18 | throw new Error("Invalid Shopify URL"); 19 | } 20 | const [, path, size, width, height, crop, extension, format] = match; 21 | 22 | url.pathname = `${path}${extension}`; 23 | 24 | const widthString = width ? width : url.searchParams.get("width"); 25 | const heightString = height ? height : url.searchParams.get("height"); 26 | url.searchParams.delete("width"); 27 | url.searchParams.delete("height"); 28 | return { 29 | base: url.toString(), 30 | width: Number(widthString) || undefined, 31 | height: Number(heightString) || undefined, 32 | format: format ? format.slice(1) : undefined, 33 | params: { crop, size }, 34 | cdn: "shopify", 35 | }; 36 | }; 37 | 38 | export const generate: UrlGenerator<{ crop?: string }> = ( 39 | { base, width, height, format, params }, 40 | ) => { 41 | const url = new URL(base); 42 | setParamIfDefined(url, "width", width, true, true); 43 | setParamIfDefined(url, "height", height, true, true); 44 | setParamIfDefined(url, "crop", params?.crop); 45 | setParamIfDefined(url, "format", format); 46 | return url; 47 | }; 48 | 49 | export const transform: UrlTransformer = ( 50 | { url: originalUrl, width, height }, 51 | ) => { 52 | const parsed = parse(originalUrl); 53 | if (!parsed) { 54 | return; 55 | } 56 | 57 | const props: UrlGeneratorOptions<{ crop?: string }> = { 58 | ...parsed, 59 | width, 60 | height, 61 | }; 62 | 63 | return generate(props); 64 | }; 65 | -------------------------------------------------------------------------------- /src/transformers/contentful.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 2 | 3 | import { transform } from "./contentful.ts"; 4 | 5 | const img = 6 | "https://images.ctfassets.net/aaaa/xxxx/yyyy/how-to-wow-a-customer.jpg"; 7 | 8 | Deno.test("contentful", async (t) => { 9 | await t.step("should format a URL", () => { 10 | const result = transform({ 11 | url: img, 12 | width: 200, 13 | height: 100, 14 | }); 15 | assertEquals( 16 | result?.toString(), 17 | "https://images.ctfassets.net/aaaa/xxxx/yyyy/how-to-wow-a-customer.jpg?w=200&h=100&fit=fill", 18 | ); 19 | }); 20 | await t.step("should not set height if not provided", () => { 21 | const result = transform({ url: img, width: 200 }); 22 | assertEquals( 23 | result?.toString(), 24 | "https://images.ctfassets.net/aaaa/xxxx/yyyy/how-to-wow-a-customer.jpg?w=200&fit=fill", 25 | ); 26 | }); 27 | await t.step("should delete height if not set", () => { 28 | const url = new URL(img); 29 | url.searchParams.set("h", "100"); 30 | const result = transform({ url, width: 200 }); 31 | assertEquals( 32 | result?.toString(), 33 | "https://images.ctfassets.net/aaaa/xxxx/yyyy/how-to-wow-a-customer.jpg?w=200&fit=fill", 34 | ); 35 | }); 36 | 37 | await t.step("should round non-integer params", () => { 38 | const result = transform({ 39 | url: img, 40 | width: 200.6, 41 | height: 100.2, 42 | }); 43 | assertEquals( 44 | result?.toString(), 45 | "https://images.ctfassets.net/aaaa/xxxx/yyyy/how-to-wow-a-customer.jpg?w=201&h=100&fit=fill", 46 | ); 47 | }); 48 | 49 | await t.step("should not set fit=fill if another value exists", () => { 50 | const url = new URL(img); 51 | url.searchParams.set("fit", "crop"); 52 | const result = transform({ url, width: 200 }); 53 | assertEquals( 54 | result?.toString(), 55 | "https://images.ctfassets.net/aaaa/xxxx/yyyy/how-to-wow-a-customer.jpg?fit=crop&w=200", 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/transform.ts: -------------------------------------------------------------------------------- 1 | import { getImageCdnForUrl } from "./detect.ts"; 2 | import { transform as contentful } from "./transformers/contentful.ts"; 3 | import { transform as builder } from "./transformers/builder.ts"; 4 | import { transform as imgix } from "./transformers/imgix.ts"; 5 | import { transform as shopify } from "./transformers/shopify.ts"; 6 | import { transform as wordpress } from "./transformers/wordpress.ts"; 7 | import { transform as cloudinary } from "./transformers/cloudinary.ts"; 8 | import { transform as cloudflare } from "./transformers/cloudflare.ts"; 9 | import { transform as bunny } from "./transformers/bunny.ts"; 10 | import { transform as storyblok } from "./transformers/storyblok.ts"; 11 | import { ImageCdn, SupportedImageCdn, UrlTransformer } from "./types.ts"; 12 | 13 | export const transformers = { 14 | imgix, 15 | contentful, 16 | "builder.io": builder, 17 | shopify, 18 | wordpress, 19 | cloudinary, 20 | bunny, 21 | storyblok, 22 | cloudflare, 23 | }; 24 | 25 | export const cdnIsSupportedForTransform = ( 26 | cdn: ImageCdn | false, 27 | ): cdn is SupportedImageCdn => cdn && cdn in transformers; 28 | 29 | /** 30 | * Returns a transformer function if the given URL is from a known image CDN 31 | */ 32 | export const getTransformerForUrl = ( 33 | url: string | URL, 34 | ): UrlTransformer | undefined => getTransformerForCdn(getImageCdnForUrl(url)); 35 | 36 | /** 37 | * Returns a transformer function if the given CDN is supported 38 | */ 39 | export const getTransformerForCdn = ( 40 | cdn: ImageCdn | false | undefined, 41 | ): UrlTransformer | undefined => { 42 | if (!cdn || !cdnIsSupportedForTransform(cdn)) { 43 | return undefined; 44 | } 45 | return transformers[cdn]; 46 | }; 47 | 48 | /** 49 | * Transforms an image URL to a new URL with the given options. 50 | * If the URL is not from a known image CDN it returns undefined. 51 | */ 52 | export const transformUrl: UrlTransformer = (options) => { 53 | if (options.cdn) { 54 | return getTransformerForCdn(options.cdn)?.(options); 55 | } 56 | return getTransformerForUrl(options.url)?.(options); 57 | }; 58 | -------------------------------------------------------------------------------- /src/transformers/builder.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 2 | 3 | import { transform } from "./builder.ts"; 4 | 5 | const img = 6 | "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f"; 7 | 8 | Deno.test("builder.io", async (t) => { 9 | await t.step("should format a URL", () => { 10 | const result = transform({ 11 | url: img, 12 | width: 200, 13 | height: 100, 14 | }); 15 | assertEquals( 16 | result?.toString(), 17 | "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=cover&width=200&height=100", 18 | ); 19 | }); 20 | await t.step("should not set height if not provided", () => { 21 | const result = transform({ url: img, width: 200 }); 22 | assertEquals( 23 | result?.toString(), 24 | "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=cover&width=200", 25 | ); 26 | }); 27 | await t.step("should delete height if not set", () => { 28 | const url = new URL(img); 29 | url.searchParams.set("height", "100"); 30 | const result = transform({ url, width: 200 }); 31 | assertEquals( 32 | result?.toString(), 33 | "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=cover&width=200", 34 | ); 35 | }); 36 | 37 | await t.step("should round non-integer params", () => { 38 | const result = transform({ 39 | url: img, 40 | width: 200.6, 41 | height: 100.2, 42 | }); 43 | assertEquals( 44 | result?.toString(), 45 | "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=cover&width=201&height=100", 46 | ); 47 | }); 48 | 49 | await t.step("should not set fit=cover if another value exists", () => { 50 | const url = new URL(img); 51 | url.searchParams.set("fit", "inside"); 52 | const result = transform({ url, width: 200 }); 53 | assertEquals( 54 | result?.toString(), 55 | "https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F462d29d57dda42cb9e26441501db535f?fit=inside&width=200", 56 | ); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /demo/src/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | 4 | } 5 | html, 6 | body { 7 | font: 16px/1.21 'Helvetica Neue', arial, sans-serif; 8 | font-weight: 400; 9 | } 10 | 11 | body { 12 | background: linear-gradient(180deg, white 0%, rgba(216,216,237,1) 81%) no-repeat center center fixed; 13 | background-size: cover; 14 | } 15 | 16 | h1, 17 | .instructions { 18 | text-align: center; 19 | } 20 | 21 | .result { 22 | grid-area: 3 / 1 / 4 / 4; 23 | } 24 | 25 | .result input { 26 | font-size: 14px; 27 | color: #666; 28 | } 29 | 30 | details { 31 | grid-area: 4 / 1 / 4 / 4; 32 | } 33 | 34 | .tools { 35 | background-color: white; 36 | display: grid; 37 | grid-template-columns: repeat(3, 1fr); 38 | grid-column-gap: 10px; 39 | grid-row-gap: 15px; 40 | max-width: 800px; 41 | margin: 2em auto; 42 | padding: 1.5em; 43 | box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.2); 44 | border-radius: 10px; 45 | } 46 | 47 | .tools>div, 48 | details { 49 | justify-content: stretch; 50 | display: grid; 51 | } 52 | 53 | label { 54 | display: block; 55 | } 56 | 57 | label, 58 | summary { 59 | text-transform: uppercase; 60 | font-size: 12px; 61 | font-weight: 700; 62 | margin-bottom: 5px; 63 | } 64 | 65 | 66 | .url { 67 | grid-area: 1 / 1 / 2 / 4; 68 | } 69 | 70 | 71 | .imagePanel { 72 | display: grid; 73 | place-items: center; 74 | } 75 | 76 | .imagePanel img { 77 | object-fit: cover; 78 | } 79 | 80 | .code { 81 | overflow: auto; 82 | } 83 | 84 | input, 85 | select { 86 | font-size: 18px; 87 | padding: 5px 10px; 88 | border: 2px #898 solid; 89 | border-radius: 2px; 90 | justify-self: stretch; 91 | } 92 | 93 | 94 | summary { 95 | cursor: pointer; 96 | } 97 | 98 | details { 99 | font-size: 14px; 100 | } 101 | 102 | @media (max-width: 850px) { 103 | 104 | .tools { 105 | grid-template-columns: 2fr; 106 | margin: 0; 107 | box-shadow: none; 108 | padding: 5px; 109 | } 110 | 111 | .tools>* { 112 | grid-area: auto; 113 | 114 | } 115 | 116 | .tools>div, 117 | details { 118 | padding: 0; 119 | } 120 | 121 | details { 122 | grid-area: auto; 123 | overflow: auto; 124 | font-size: 12px; 125 | } 126 | 127 | } 128 | 129 | img { 130 | border: 1px solid #ccc; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/parse.ts: -------------------------------------------------------------------------------- 1 | import { getImageCdnForUrl } from "./detect.ts"; 2 | import { parse as contentful } from "./transformers/contentful.ts"; 3 | import { parse as builder } from "./transformers/builder.ts"; 4 | import { parse as imgix } from "./transformers/imgix.ts"; 5 | import { parse as shopify } from "./transformers/shopify.ts"; 6 | import { parse as wordpress } from "./transformers/wordpress.ts"; 7 | import { parse as cloudinary } from "./transformers/cloudinary.ts"; 8 | import { parse as cloudflare } from "./transformers/cloudflare.ts"; 9 | import { parse as bunny } from "./transformers/bunny.ts"; 10 | import { parse as storyblok } from "./transformers/storyblok.ts"; 11 | import { ImageCdn, ParsedUrl, SupportedImageCdn, UrlParser } from "./types.ts"; 12 | 13 | export const parsers = { 14 | imgix, 15 | contentful, 16 | "builder.io": builder, 17 | shopify, 18 | wordpress, 19 | cloudinary, 20 | cloudflare, 21 | bunny, 22 | storyblok, 23 | }; 24 | 25 | export const cdnIsSupportedForParse = ( 26 | cdn: ImageCdn | false, 27 | ): cdn is SupportedImageCdn => cdn && cdn in parsers; 28 | 29 | /** 30 | * Returns a parser function if the given URL is from a known image CDN 31 | * @param url 32 | */ 33 | export const getParserForUrl = >( 34 | url: string | URL, 35 | ): UrlParser | undefined => 36 | getParserForCdn(getImageCdnForUrl(url)); 37 | 38 | export const getParserForCdn = >( 39 | cdn: ImageCdn | false | undefined, 40 | ): UrlParser | undefined => { 41 | if (!cdn || !cdnIsSupportedForParse(cdn)) { 42 | return undefined; 43 | } 44 | return parsers[cdn] as UrlParser; 45 | }; 46 | 47 | /** 48 | * Parses an image URL into its components. 49 | * If the URL is not from a known image CDN it returns undefined. 50 | * @param url 51 | */ 52 | export const parseUrl = >( 53 | url: string | URL, 54 | cdn?: ImageCdn, 55 | ): ParsedUrl | undefined => { 56 | if (cdn) { 57 | return getParserForCdn(cdn)?.(url) as ParsedUrl; 58 | } 59 | const detectedCdn = getImageCdnForUrl(url); 60 | if (!detectedCdn) { 61 | return undefined; 62 | } 63 | if (!cdnIsSupportedForParse(detectedCdn)) { 64 | return { cdn: detectedCdn, base: url.toString() } as ParsedUrl; 65 | } 66 | return getParserForCdn(detectedCdn)?.(url) as ParsedUrl; 67 | }; 68 | -------------------------------------------------------------------------------- /src/transformers/storyblok.test.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 3 | import { parse } from "./storyblok.ts"; 4 | 5 | const images = [ 6 | "https://a.storyblok.com/f/39898/1000x600/d962430746/demo-image-human.jpeg/m/100x100:450x350/200x200/filters:grayscale()", 7 | "https://a.storyblok.com/f/39898/2250x1500/c15735a73c/demo-image-human.jpeg/m/600x130", 8 | "https://a.storyblok.com/f/39898/1000x600/d962430746/demo-image-human.jpeg/m/-230x230/filters:rotate(90)", 9 | "https://a.storyblok.com/f/39898/1000x600/d962430746/demo-image-human.jpeg/m/-230x230/filters:format(webp):rotate(90)", 10 | "https://a.storyblok.com/f/39898/1000x600/d962430746/demo-image-human.jpeg", 11 | "https://img2.storyblok.com/100x100:450x350/200x200/filters:grayscale()/f/39898/1000x600/d962430746/demo-image-human.jpeg", 12 | "https://img2.storyblok.com/600x-130/f/39898/2250x1500/c15735a73c/demo-image-human.jpeg", 13 | "https://img2.storyblok.com/-230x230/filters:rotate(90)/f/39898/1000x600/d962430746/demo-image-human.jpeg", 14 | "https://img2.storyblok.com/200x0/filters:format(png)/f/39898/3310x2192/e4ec08624e/demo-image.jpeg", 15 | "https://img2.storyblok.com/200x0/filters:rotate(90):format(png)/f/39898/3310x2192/e4ec08624e/demo-image.jpeg", 16 | "https://img2.storyblok.com/f/39898/3310x2192/e4ec08624e/demo-image.jpeg", 17 | ]; 18 | 19 | Deno.test("storyblok parser", async (t) => { 20 | for (const image of images) { 21 | await t.step(image, () => { 22 | const res = parse(image); 23 | console.log(res); 24 | }); 25 | // await t.step(original, () => { 26 | // const { params, ...parsed } = parse(original ) as any; 27 | // // Convert null from JSON into undefined for assertEquals 28 | // const expected = Object.fromEntries( 29 | // Object.entries(example).map(([k, v]) => [k, v ?? undefined]), 30 | // ); 31 | // expected.cdn = "shopify"; 32 | // const { crop, size } = params || {}; 33 | // assertEquals({ crop, size, ...parsed }, expected); 34 | // }); 35 | } 36 | }); 37 | 38 | // Deno.test("shopify transformer", async (t) => { 39 | // await t.step("transforms a URL", () => { 40 | // const result = transform({ 41 | // url: img, 42 | // width: 100, 43 | // height: 200, 44 | // }); 45 | // assertEquals( 46 | // result?.toString(), 47 | // "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.webp?v=3&width=100&height=200&crop=top", 48 | // ); 49 | // }); 50 | // }); 51 | -------------------------------------------------------------------------------- /src/transformers/shopify.fixtures.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage_icon.png?v=1", 4 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.png?v=1", 5 | "width": null, 6 | "height": null, 7 | "size": "icon", 8 | "crop": null, 9 | "format": null 10 | }, 11 | { 12 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage_200x300.jpg?v=2", 13 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.jpg?v=2", 14 | "width": 200, 15 | "height": 300, 16 | "size": null, 17 | "crop": null, 18 | "format": null 19 | }, 20 | { 21 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage_medium_crop_top.webp?v=3", 22 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.webp?v=3", 23 | "width": null, 24 | "height": null, 25 | "size": "medium", 26 | "crop": "top", 27 | "format": null 28 | }, 29 | { 30 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage_large_crop_bottom.avif?v=4", 31 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.avif?v=4", 32 | "width": null, 33 | "height": null, 34 | "size": "large", 35 | "crop": "bottom", 36 | "format": null 37 | }, 38 | { 39 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.jpg?v=5", 40 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage.jpg?v=5", 41 | "width": null, 42 | "height": null, 43 | "size": null, 44 | "crop": null, 45 | "format": null 46 | }, 47 | { 48 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage2_icon.png.jpg?v=6", 49 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage2.png?v=6", 50 | "width": null, 51 | "height": null, 52 | "size": "icon", 53 | "crop": null, 54 | "format": "jpg" 55 | }, 56 | { 57 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage2_200x300.jpg.webp?v=7", 58 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage2.jpg?v=7", 59 | "width": 200, 60 | "height": 300, 61 | "size": null, 62 | "crop": null, 63 | "format": "webp" 64 | }, 65 | { 66 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage2_medium_crop_top.webp.avif?v=8", 67 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage2.webp?v=8", 68 | "width": null, 69 | "height": null, 70 | "size": "medium", 71 | "crop": "top", 72 | "format": "avif" 73 | }, 74 | { 75 | "original": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage2_large_crop_bottom.avif.png?v=9", 76 | "base": "https://cdn.shopify.com/s/files/1/2345/6789/products/myimage2.avif?v=9", 77 | "width": null, 78 | "height": null, 79 | "size": "large", 80 | "crop": "bottom", 81 | "format": "png" 82 | } 83 | ] 84 | -------------------------------------------------------------------------------- /src/transformers/cloudflare.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UrlGenerator, 3 | UrlGeneratorOptions, 4 | UrlParser, 5 | UrlTransformer, 6 | } from "../types.ts"; 7 | 8 | const cloudflareRegex = 9 | /https?:\/\/(?[^\/]+)\/cdn-cgi\/image\/(?[^\/]+)\/(?.*)$/g; 10 | 11 | const parseTransforms = (transformations: string) => 12 | Object.fromEntries(transformations.split(",").map((t) => t.split("="))); 13 | 14 | const formatUrl = ( 15 | { 16 | host, 17 | transformations = {}, 18 | path 19 | }: CloudflareParams, 20 | ): string => { 21 | const transformString = Object.entries(transformations).map( 22 | ([key, value]) => `${key}=${value}`, 23 | ).join(","); 24 | 25 | const pathSegments = [ 26 | host, 27 | "cdn-cgi", 28 | "image", 29 | transformString, 30 | path 31 | ].join("/"); 32 | return `https://${pathSegments}`; 33 | }; 34 | 35 | export interface CloudflareParams { 36 | host?: string; 37 | transformations: Record; 38 | path?: string; 39 | } 40 | export const parse: UrlParser = ( 41 | imageUrl, 42 | ) => { 43 | const url = new URL(imageUrl); 44 | const matches = [...url.toString().matchAll(cloudflareRegex)]; 45 | if (!matches.length) { 46 | throw new Error("Invalid Cloudflare URL"); 47 | } 48 | 49 | const group = matches[0].groups || {}; 50 | const { 51 | transformations: transformString, 52 | ...baseParams 53 | } = group; 54 | 55 | const { width, height, f, ...transformations } = parseTransforms( 56 | transformString, 57 | ); 58 | 59 | const base = formatUrl({ ...baseParams, transformations }); 60 | return { 61 | base: url.toString(), 62 | width: Number(width) || undefined, 63 | height: Number(height) || undefined, 64 | format: f, 65 | cdn: "cloudflare", 66 | params: { ...group, transformations }, 67 | }; 68 | }; 69 | 70 | export const generate: UrlGenerator = ( 71 | { base, width, height, format, params }, 72 | ) => { 73 | const parsed = parse(base.toString()); 74 | 75 | const props: CloudflareParams = { 76 | transformations: {}, 77 | ...parsed.params, 78 | ...params, 79 | }; 80 | 81 | if (width) { 82 | props.transformations.width = width?.toString(); 83 | } 84 | if (height) { 85 | props.transformations.height = height?.toString(); 86 | } 87 | if (format) { 88 | props.transformations.f = format; 89 | } 90 | return new URL(formatUrl(props)); 91 | }; 92 | 93 | export const transform: UrlTransformer = ( 94 | { url: originalUrl, width, height, format = "auto" }, 95 | ) => { 96 | const parsed = parse(originalUrl); 97 | if (!parsed) { 98 | throw new Error("Invalid Cloudflare URL"); 99 | } 100 | 101 | const props: UrlGeneratorOptions = { 102 | ...parsed, 103 | width, 104 | height, 105 | format, 106 | }; 107 | 108 | return generate(props); 109 | }; 110 | -------------------------------------------------------------------------------- /src/transformers/imgix.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 2 | import { transform } from "./imgix.ts"; 3 | 4 | const img = 5 | "https://images.unsplash.com/photo?auto=format&fit=crop&w=2089&q=80"; 6 | 7 | Deno.test("imgix", async (t) => { 8 | await t.step("should format a URL", () => { 9 | const result = transform({ 10 | url: img, 11 | width: 200, 12 | height: 100, 13 | }); 14 | assertEquals( 15 | result?.toString(), 16 | "https://images.unsplash.com/photo?auto=format&fit=crop&w=200&q=80&h=100", 17 | ); 18 | }); 19 | 20 | await t.step("should not set height if not provided", () => { 21 | const result = transform({ url: img, width: 200 }); 22 | assertEquals( 23 | result?.toString(), 24 | "https://images.unsplash.com/photo?auto=format&fit=crop&w=200&q=80", 25 | ); 26 | }); 27 | 28 | await t.step("should delete height if not set", () => { 29 | const url = new URL(img); 30 | url.searchParams.set("h", "100"); 31 | const result = transform({ url, width: 200 }); 32 | assertEquals( 33 | result?.toString(), 34 | "https://images.unsplash.com/photo?auto=format&fit=crop&w=200&q=80", 35 | ); 36 | }); 37 | 38 | await t.step("should round non-integer dimensions", () => { 39 | const result = transform({ 40 | url: img, 41 | width: 200.6, 42 | height: 100.2, 43 | }); 44 | assertEquals( 45 | result?.toString(), 46 | "https://images.unsplash.com/photo?auto=format&fit=crop&w=201&q=80&h=100", 47 | ); 48 | }); 49 | 50 | await t.step("should set auto=format if no format is provided", () => { 51 | const url = new URL(img); 52 | url.searchParams.delete("auto"); 53 | const result = transform({ url: img, width: 200 }); 54 | assertEquals( 55 | result?.toString(), 56 | "https://images.unsplash.com/photo?auto=format&fit=crop&w=200&q=80", 57 | ); 58 | }); 59 | 60 | await t.step("should not set auto=format if format is provided", () => { 61 | const url = new URL(img); 62 | url.searchParams.delete("auto"); 63 | const result = transform({ url, width: 200, format: "jpg" }); 64 | assertEquals( 65 | result?.toString(), 66 | "https://images.unsplash.com/photo?fit=crop&w=200&q=80&fm=jpg", 67 | ); 68 | }); 69 | 70 | await t.step("should delete auto=format if format is provided", () => { 71 | const result = transform({ url: img, width: 200, format: "jpg" }); 72 | assertEquals( 73 | result?.toString(), 74 | "https://images.unsplash.com/photo?fit=crop&w=200&q=80&fm=jpg", 75 | ); 76 | }); 77 | 78 | await t.step( 79 | "should remove format from existing auto value if format is provided", 80 | () => { 81 | const url = new URL(img); 82 | url.searchParams.set("auto", "compress,format"); 83 | const result = transform({ url, width: 200, format: "jpg" }); 84 | assertEquals( 85 | result?.toString(), 86 | "https://images.unsplash.com/photo?auto=compress&fit=crop&w=200&q=80&fm=jpg", 87 | ); 88 | }, 89 | ); 90 | }); 91 | -------------------------------------------------------------------------------- /demo/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { h } from "preact"; 2 | import { parseUrl, transformUrl } from "../../mod"; 3 | import { computed, signal } from "@preact/signals"; 4 | import "./style.css"; 5 | import examples from "./examples.json"; 6 | 7 | const inputUrl = signal(examples[0][1]); 8 | 9 | const width = signal(800); 10 | const height = signal(600); 11 | 12 | const url = computed(() => { 13 | return transformUrl({ 14 | url: inputUrl.value, 15 | width: width.value, 16 | height: height.value, 17 | }); 18 | }); 19 | 20 | const parsedUrl = computed(() => 21 | JSON.stringify(parseUrl(inputUrl.value), null, 2) 22 | ); 23 | 24 | const code = computed(() => 25 | /* javascript */ `const url = transformUrl({ 26 | url: ${JSON.stringify(inputUrl.value)}, 27 | width: ${width}, 28 | height: ${height}, 29 | });` 30 | ); 31 | 32 | export default function App() { 33 | return ( 34 |
35 |

🖼 Unpic

36 |

37 | Enter an image URL below, or choose from one of the examples 38 |

39 |
40 |
41 | 44 | inputUrl.value = e.target.value} 49 | /> 50 |
51 |
52 | 55 | 61 |
62 |
63 | 66 | width.value = e.target.value} 72 | /> 73 |
74 |
75 | 78 | height.value = e.target.value} 84 | /> 85 |
86 |
87 | 90 | 91 |
92 |
93 | Show details 94 |
95 |
96 |
Result
97 |
{parsedUrl}
98 |
99 |
100 |
Code
101 | 102 |
{code}
103 |
104 |
105 |
106 |
107 | 108 |
109 | {url.value 110 | ? 111 | :

Invalid URL

} 112 |
113 |
114 | ); 115 | } 116 | -------------------------------------------------------------------------------- /src/transformers/storyblok.ts: -------------------------------------------------------------------------------- 1 | import { UrlGenerator, UrlParser, UrlTransformer } from "../types.ts"; 2 | 3 | const storyBlokAssets = 4 | /(?\/f\/\d+\/\d+x\d+\/\w+\/[^\/]+)\/?(?m\/?(?\d+x\d+:\d+x\d+)?\/?(?(?\-)?(?\d+)x(?\-)?(?\d+))?\/?(filters\:(?[^\/]+))?)?$/g; 5 | 6 | const storyBlokImg2 = 7 | /^(?\/(?\d+x\d+:\d+x\d+)?\/?(?(?\-)?(?\d+)x(?\-)?(?\d+))?\/?(filters\:(?[^\/]+))?\/?)?(?\/f\/.+)$/g; 8 | 9 | export interface StoryblokParams { 10 | crop?: string; 11 | filters?: Record; 12 | flipx?: "-"; 13 | flipy?: "-"; 14 | } 15 | 16 | export const splitFilters = (filters: string): Record => { 17 | if (!filters) { 18 | return {}; 19 | } 20 | return Object.fromEntries( 21 | filters.split(":").map((filter) => { 22 | if (!filter) return []; 23 | const [key, value] = filter.split("("); 24 | return [key, value.replace(")", "")]; 25 | }), 26 | ); 27 | }; 28 | 29 | export const generateFilters = (filters?: Record) => { 30 | if (!filters) { 31 | return undefined; 32 | } 33 | const filterItems = Object.entries(filters).map(([key, value]) => 34 | `${key}(${value ?? ""})` 35 | ); 36 | if (filterItems.length === 0) { 37 | return undefined; 38 | } 39 | return `filters:${filterItems.join(":")}`; 40 | }; 41 | 42 | export const parse: UrlParser = ( 43 | imageUrl, 44 | ) => { 45 | const url = new URL(imageUrl); 46 | 47 | // img2.storyblok.com is the old domain for Storyblok images, which used a 48 | // different path format. We'll assume custom domains are using the new format. 49 | const regex = url.hostname === "img2.storyblok.com" 50 | ? storyBlokImg2 51 | : storyBlokAssets; 52 | 53 | const [matches] = url.pathname.matchAll(regex); 54 | if (!matches || !matches.groups) { 55 | throw new Error("Invalid Storyblok URL"); 56 | } 57 | 58 | const { id, crop, width, height, filters, flipx, flipy } = matches.groups; 59 | 60 | const { format, ...filterMap } = splitFilters(filters); 61 | 62 | // We update old img2.storyblok.com URLs to use the new syntax and domain 63 | if (url.hostname === "img2.storyblok.com") { 64 | url.hostname = "a.storyblok.com"; 65 | } 66 | 67 | return { 68 | base: url.origin + id, 69 | width: Number(width) || undefined, 70 | height: Number(height) || undefined, 71 | format, 72 | params: { 73 | crop, 74 | filters: filterMap, 75 | flipx: flipx as "-" | undefined, 76 | flipy: flipy as "-" | undefined, 77 | }, 78 | cdn: "storyblok", 79 | }; 80 | }; 81 | 82 | export const generate: UrlGenerator = ( 83 | { base, width = 0, height = 0, format, params = {} }, 84 | ) => { 85 | const { crop, filters, flipx = "", flipy = "" } = params; 86 | 87 | const size = `${flipx}${width}x${flipy}${height}`; 88 | 89 | return new URL( 90 | [base, "m", crop, size, generateFilters(filters), format].filter( 91 | Boolean, 92 | ).join("/"), 93 | ); 94 | }; 95 | 96 | export const transform: UrlTransformer = ( 97 | { url: originalUrl, width, height, format }, 98 | ) => { 99 | const parsed = parse(originalUrl); 100 | if (!parsed) { 101 | return; 102 | } 103 | 104 | if (format) { 105 | if (!parsed.params) { 106 | parsed.params = { filters: {} }; 107 | } 108 | if (!parsed.params.filters) { 109 | parsed.params.filters = {}; 110 | } 111 | parsed.params.filters.format = format; 112 | } 113 | 114 | return generate({ 115 | ...parsed, 116 | width, 117 | height, 118 | }); 119 | }; 120 | -------------------------------------------------------------------------------- /src/transformers/cloudinary.ts: -------------------------------------------------------------------------------- 1 | import { 2 | UrlGenerator, 3 | UrlGeneratorOptions, 4 | UrlParser, 5 | UrlTransformer, 6 | } from "../types.ts"; 7 | import { roundIfNumeric } from "../utils.ts"; 8 | 9 | // Thanks Colby! 10 | const cloudinaryRegex = 11 | /https?:\/\/(?[^\/]+)\/(?[^\/]+)\/(?image|video|raw)\/(?upload|fetch|private|authenticated|sprite|facebook|twitter|youtube|vimeo)\/?(?s\-\-[a-zA-Z0-9]+\-\-)?\/?(?(?:[^_\/]+_[^,\/]+,?)*)?\/(?:(?v\d+)\/)?(?[^\.^\s]+)\.?(?[a-zA-Z]+$)?$/g; 12 | 13 | const parseTransforms = (transformations: string) => { 14 | return transformations 15 | ? Object.fromEntries(transformations.split(",").map((t) => t.split("_"))) 16 | : {}; 17 | }; 18 | 19 | const formatUrl = ( 20 | { 21 | host, 22 | cloudName, 23 | assetType, 24 | deliveryType, 25 | signature, 26 | transformations = {}, 27 | version, 28 | id, 29 | format, 30 | }: CloudinaryParams, 31 | ): string => { 32 | if (format) { 33 | transformations.f = format; 34 | } 35 | const transformString = Object.entries(transformations).map( 36 | ([key, value]) => `${key}_${value}`, 37 | ).join(","); 38 | 39 | const pathSegments = [ 40 | host, 41 | cloudName, 42 | assetType, 43 | deliveryType, 44 | signature, 45 | transformString, 46 | version, 47 | id, 48 | ].filter(Boolean).join("/"); 49 | return `https://${pathSegments}`; 50 | }; 51 | 52 | export interface CloudinaryParams { 53 | host?: string; 54 | cloudName?: string; 55 | assetType?: string; 56 | deliveryType?: string; 57 | signature?: string; 58 | transformations: Record; 59 | version?: string; 60 | id?: string; 61 | format?: string; 62 | } 63 | export const parse: UrlParser = ( 64 | imageUrl, 65 | ) => { 66 | const url = new URL(imageUrl); 67 | const matches = [...url.toString().matchAll(cloudinaryRegex)]; 68 | if (!matches.length) { 69 | throw new Error("Invalid Cloudinary URL"); 70 | } 71 | 72 | const group = matches[0].groups || {}; 73 | const { 74 | transformations: transformString, 75 | format: originalFormat, 76 | ...baseParams 77 | } = group; 78 | 79 | const { w, h, f, ...transformations } = parseTransforms( 80 | transformString, 81 | ); 82 | 83 | const format = (f && f !== "auto") ? f : originalFormat; 84 | 85 | const base = formatUrl({ ...baseParams, transformations }); 86 | return { 87 | base, 88 | width: Number(w) || undefined, 89 | height: Number(h) || undefined, 90 | format, 91 | cdn: "cloudinary", 92 | params: { ...group, transformations }, 93 | }; 94 | }; 95 | 96 | export const generate: UrlGenerator = ( 97 | { base, width, height, format, params }, 98 | ) => { 99 | const parsed = parse(base.toString()); 100 | 101 | const props: CloudinaryParams = { 102 | transformations: {}, 103 | ...parsed.params, 104 | ...params, 105 | format: format || "auto", 106 | }; 107 | 108 | if (width) { 109 | props.transformations.w = roundIfNumeric(width).toString(); 110 | } 111 | if (height) { 112 | props.transformations.h = roundIfNumeric(height).toString(); 113 | } 114 | return new URL(formatUrl(props)); 115 | }; 116 | 117 | export const transform: UrlTransformer = ( 118 | { url: originalUrl, width, height, format = "auto" }, 119 | ) => { 120 | const parsed = parse(originalUrl); 121 | if (!parsed) { 122 | throw new Error("Invalid Cloudinary URL"); 123 | } 124 | 125 | if (parsed.params?.assetType !== "image") { 126 | throw new Error("Cloudinary transformer only supports images"); 127 | } 128 | 129 | if (parsed.params?.signature) { 130 | throw new Error( 131 | "Cloudinary transformer does not support signed URLs", 132 | ); 133 | } 134 | 135 | const props: UrlGeneratorOptions = { 136 | ...parsed, 137 | width, 138 | height, 139 | format, 140 | }; 141 | 142 | return generate(props); 143 | }; 144 | -------------------------------------------------------------------------------- /src/transformers/cloudinary.test.ts: -------------------------------------------------------------------------------- 1 | import { assertEquals } from "https://deno.land/std@0.172.0/testing/asserts.ts"; 2 | import { ParsedUrl } from "../types.ts"; 3 | import { CloudinaryParams, parse, transform } from "./cloudinary.ts"; 4 | 5 | const img = 6 | "https://res.cloudinary.com/demo/image/upload/c_lfill,w_800,h_550,f_auto/dog.webp"; 7 | 8 | const imgNoTransforms = "https://res.cloudinary.com/demo/image/upload/dog.jpg"; 9 | 10 | const imgWithPath = 11 | "https://res.cloudinary.com/demo/image/upload/b_rgb:FFFFFF,c_fill,dpr_2.0,f_auto,g_auto,h_600,q_auto,w_600/v1/Product%20gallery%20demo/New%20Demo%20Pages/Tshirt/tshirt1"; 12 | 13 | Deno.test("cloudinary parser", async (t) => { 14 | await t.step("parses a URL", () => { 15 | const parsed = parse(img); 16 | const expected: ParsedUrl = { 17 | base: "https://res.cloudinary.com/demo/image/upload/c_lfill/dog", 18 | cdn: "cloudinary", 19 | format: "webp", 20 | width: 800, 21 | height: 550, 22 | params: { 23 | assetType: "image", 24 | cloudName: "demo", 25 | deliveryType: "upload", 26 | format: "webp", 27 | host: "res.cloudinary.com", 28 | id: "dog", 29 | signature: undefined, 30 | transformations: { 31 | c: "lfill", 32 | }, 33 | version: undefined, 34 | }, 35 | }; 36 | assertEquals(parsed, expected); 37 | }); 38 | 39 | await t.step("parses a URL without transforms", () => { 40 | const parsed = parse(imgNoTransforms); 41 | const expected: ParsedUrl = { 42 | base: "https://res.cloudinary.com/demo/image/upload/dog", 43 | cdn: "cloudinary", 44 | format: "jpg", 45 | width: undefined, 46 | height: undefined, 47 | params: { 48 | assetType: "image", 49 | cloudName: "demo", 50 | deliveryType: "upload", 51 | format: "jpg", 52 | host: "res.cloudinary.com", 53 | id: "dog", 54 | signature: undefined, 55 | transformations: {}, 56 | version: undefined, 57 | }, 58 | }; 59 | assertEquals(parsed, expected); 60 | }); 61 | 62 | await t.step("parses a URL with version and folder path", () => { 63 | const parsed = parse(imgWithPath); 64 | const expected: ParsedUrl = { 65 | base: 66 | "https://res.cloudinary.com/demo/image/upload/b_rgb:FFFFFF,c_fill,dpr_2.0,g_auto,q_auto/v1/Product%20gallery%20demo/New%20Demo%20Pages/Tshirt/tshirt1", 67 | cdn: "cloudinary", 68 | format: undefined, 69 | width: 600, 70 | height: 600, 71 | params: { 72 | assetType: "image", 73 | cloudName: "demo", 74 | deliveryType: "upload", 75 | format: undefined, 76 | host: "res.cloudinary.com", 77 | id: "Product%20gallery%20demo/New%20Demo%20Pages/Tshirt/tshirt1", 78 | signature: undefined, 79 | transformations: { 80 | b: "rgb:FFFFFF", 81 | c: "fill", 82 | dpr: "2.0", 83 | g: "auto", 84 | q: "auto", 85 | }, 86 | version: "v1", 87 | }, 88 | }; 89 | assertEquals(parsed, expected); 90 | }); 91 | }); 92 | 93 | Deno.test("cloudinary transformer", async (t) => { 94 | await t.step("transforms a URL", () => { 95 | const result = transform({ 96 | url: img, 97 | width: 100, 98 | height: 200, 99 | }); 100 | assertEquals( 101 | result?.toString(), 102 | "https://res.cloudinary.com/demo/image/upload/c_lfill,w_100,h_200,f_auto/dog", 103 | ); 104 | }); 105 | 106 | await t.step("rounds non-integer values", () => { 107 | const result = transform({ 108 | url: img, 109 | width: 100.6, 110 | height: 200.2, 111 | }); 112 | assertEquals( 113 | result?.toString(), 114 | "https://res.cloudinary.com/demo/image/upload/c_lfill,w_101,h_200,f_auto/dog", 115 | ); 116 | }); 117 | 118 | await t.step("transforms a URL without parsed transforms", () => { 119 | const result = transform({ 120 | url: imgNoTransforms, 121 | width: 100, 122 | height: 200, 123 | }); 124 | assertEquals( 125 | result?.toString(), 126 | "https://res.cloudinary.com/demo/image/upload/w_100,h_200,f_auto/dog", 127 | ); 128 | }); 129 | 130 | await t.step("transforms a URL with path and version", () => { 131 | const result = transform({ 132 | url: imgWithPath, 133 | width: 100, 134 | height: 200, 135 | }); 136 | assertEquals( 137 | result?.toString(), 138 | "https://res.cloudinary.com/demo/image/upload/b_rgb:FFFFFF,c_fill,dpr_2.0,g_auto,q_auto,w_100,h_200,f_auto/v1/Product%20gallery%20demo/New%20Demo%20Pages/Tshirt/tshirt1", 139 | ); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🖼 Unpic 2 | 3 | **Universal image CDN URL translator** 4 | 5 | There are many image CDNs that provide a URL API for transforming images. There 6 | is little consistency in these APIs, and it's often unclear what the API is for 7 | a given URL. This library aims to provide a consistent interface for detecting 8 | image CDN URLs and transforming them. 9 | 10 | If you'd like to use this on the web, you might want to try 11 | [Unpic img](https://github.com/ascorbic/unpic-img), a multi-framework image 12 | component, powered by Unpic. 13 | 14 | It designed to work with image URLs from sources such as CMSs and other 15 | user-generated content, where the source image may or may not be from an image 16 | CDN, and may already have transforms applied. This allow different transforms to 17 | be applied for display on a website. A web framework may need to transform an 18 | image for display on a site. Rather than doing this by downloading and resizing 19 | it locally or re-processing it with a separate image service, this library can 20 | be used to transform the URL to use the original image CDN, which will then 21 | transform the image on the fly. 22 | 23 | ## Usage 24 | 25 | This library is available via URL imports for Deno and via npm for Node. To use 26 | it in Deno, import the module from deno.land: 27 | 28 | ```ts 29 | import { transformUrl } from "https://deno.land/x/unpic/mod.ts"; 30 | ``` 31 | 32 | To use it in Node, install it from npm: 33 | 34 | ```sh 35 | npm install unpic 36 | ``` 37 | 38 | Then import it in your code: 39 | 40 | ```ts 41 | import { transformUrl } from "unpic"; 42 | ``` 43 | 44 | You can then use the `transformUrl` function to transform a URL: 45 | 46 | ```ts 47 | const url = transformUrl( 48 | { 49 | url: 50 | "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg", 51 | width: 800, 52 | height: 600, 53 | }, 54 | ); 55 | 56 | console.log(url.toString()); 57 | 58 | // https://cdn.shopify.com/static/sample-images/bath.jpeg?width=800&height=600&crop=center 59 | ``` 60 | 61 | You can also use the `parseUrl` function to parse a URL and get the CDN and any 62 | params: 63 | 64 | ```ts 65 | const parsedUrl = parseUrl( 66 | "https://cdn.shopify.com/static/sample-images/bath_800x600_crop_center.jpeg", 67 | ); 68 | 69 | console.log(parsedUrl); 70 | // { 71 | // cdn: "shopify", 72 | // width: 800, 73 | // height: 600, 74 | // base: "https://cdn.shopify.com/static/sample-images/bath.jpeg", 75 | // params: { 76 | // crop: "center", 77 | // }, 78 | // } 79 | ``` 80 | 81 | You can bypass auto-detection by specifying the CDN: 82 | 83 | ```ts 84 | const url = transformUrl( 85 | { 86 | url: 87 | "https://cdn.shopify.com/static/sample-images/bath_grande_crop_center.jpeg", 88 | width: 800, 89 | height: 600, 90 | cdn: "shopify", 91 | }, 92 | ); 93 | ``` 94 | 95 | This is particularly useful if you are using the CDN with a custom domain which 96 | is not auto-detected. 97 | 98 | ## Supported CDN APIs 99 | 100 | - Imgix, including Unsplash, DatoCMS, Sanity and Prismic 101 | - Contentful 102 | - Builder.io 103 | - Cloudinary 104 | - Shopify 105 | - WordPress.com and Jetpack Site Accelerator 106 | - Bunny.net 107 | - Storyblok 108 | - Cloudflare 109 | 110 | ## FAQs 111 | 112 | - **What is an image CDN?** An image CDN is a service that provides a URL API 113 | for transforming images. This is often used to resize images on the fly, but 114 | can also be used to apply other transforms such as cropping, rotation, 115 | compression, etc. This includes dedicated image CDNs such as Imgix and 116 | Cloudinary, CMSs such as Contentful, Builder.io and Sanity, general CDNs such as Bunny.net 117 | that provide an image API, but also other service providers such as Shopify. 118 | The CMSs and other service providers often use a dedicated image CDN to 119 | provide the image API, most commonly Imgix. In most cases they support the 120 | same API, but in others they may proxy the image through their own CDN, or use 121 | a different API. 122 | - **Why would I use this instead of the CDN's own SDK?** If you you know that 123 | your images will all come from one CDN, then you probably should use the CDN's 124 | own SDK. This library is designed to work with images from multiple CDNs, and 125 | to work with images that may or may not be from a CDN. It is particularly 126 | useful for images that may come from an arbitrary source, such as a CMS. It is 127 | also useful for parsing URLs that may already have transforms applied, because 128 | most CDN SDKs will not parse these URLs correctly. 129 | - **Can you add support for CDN X?** If it supports a URL API and has a public 130 | domain by which it can be identified then yes, please open an issue or PR. 131 | - **Can you add my domain to CDN X?** If you provide a service where end-users 132 | use your URLs then probably. Examples may be image providers such as Unsplash, 133 | or CMSs. If it is just your own site then probably not. You can manually 134 | specify the CDN in the arguments to `transformUrl` and `parseUrl`. 135 | - **Can you support more params?** We deliberately just support the most common 136 | params that are shared between all CDNs. If you need more params then you can 137 | use the CDN-specific API directly. 138 | - **Why do you set auto format?** If the CDN support is, and no format is 139 | specified in `transformUrl`, the library will remove any format set in the 140 | source image, changing it to auto-format. In most cases, this is what you 141 | want. Almost all browsers now support modern formats such as WebP, and setting 142 | auto-format will allow the CDN to serve the best format for the browser. If 143 | you want to force a specific format, you can set it in `transformUrl`. 144 | - **Do you support SVG, animated GIF etc?** If the CDN supports it, then yes. We 145 | don't attempt to check if a format is valid - we will just pass it through to 146 | the CDN. If the CDN doesn't support it, then it will return an error or a 147 | default. 148 | - **Do you support video, etc** No, this library is only for images. If you pass 149 | a video URL to `transformUrl`, it will return `undefined`, as it will for any 150 | URL that is not recognised as an image CDN URL. It is up to you to handle this 151 | case. 152 | 153 | ## Contributing 154 | 155 | To add new domains or subdomains to an existing CDN, add them to `domains.json` 156 | or `subdomains.json` respectively. 157 | 158 | To add a new CDN, add the following: 159 | 160 | - a new source file in `src/transformers`. This should export a `transform` 161 | function that implements the `UrlTransformer` interface, a `parse` function 162 | that implements the `UrlParser` interface and optionally a `generate` function 163 | that implements the `UrlGenerator` interface. 164 | - a new test file in `src/transformers`. This should test all of the exported 165 | API functions. 166 | - at least one entry in `domains.json`, `subdomains.json` or `paths.json` to 167 | detect the CDN 168 | - add the new CDN to the types in `src/types.ts`, and import the new source file 169 | in `src/transform.ts` 170 | - add a sample image to `examples.json` in the demo site 171 | -------------------------------------------------------------------------------- /deno.lock: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2", 3 | "remote": { 4 | "https://deno.land/std@0.111.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", 5 | "https://deno.land/std@0.111.0/_util/os.ts": "dfb186cc4e968c770ab6cc3288bd65f4871be03b93beecae57d657232ecffcac", 6 | "https://deno.land/std@0.111.0/bytes/bytes_list.ts": "3bff6a09c72b2e0b1e92e29bd3b135053894196cca07a2bba842901073efe5cb", 7 | "https://deno.land/std@0.111.0/bytes/equals.ts": "69f55fdbd45c71f920c1a621e6c0865dc780cd8ae34e0f5e55a9497b70c31c1b", 8 | "https://deno.land/std@0.111.0/bytes/mod.ts": "fedb80b8da2e7ad8dd251148e65f92a04c73d6c5a430b7d197dc39588c8dda6f", 9 | "https://deno.land/std@0.111.0/fmt/colors.ts": "8368ddf2d48dfe413ffd04cdbb7ae6a1009cf0dccc9c7ff1d76259d9c61a0621", 10 | "https://deno.land/std@0.111.0/fs/_util.ts": "f2ce811350236ea8c28450ed822a5f42a0892316515b1cd61321dec13569c56b", 11 | "https://deno.land/std@0.111.0/fs/ensure_dir.ts": "b7c103dc41a3d1dbbb522bf183c519c37065fdc234831a4a0f7d671b1ed5fea7", 12 | "https://deno.land/std@0.111.0/hash/sha256.ts": "bd85257c68d1fdd9da8457284c4fbb04efa9f4f2229b5f41a638d5b71a3a8d5c", 13 | "https://deno.land/std@0.111.0/io/buffer.ts": "fdf93ba9e5d20ff3369e2c42443efd89131f8a73066f7f59c033cc588a0e2cfe", 14 | "https://deno.land/std@0.111.0/io/types.d.ts": "89a27569399d380246ca7cdd9e14d5e68459f11fb6110790cc5ecbd4ee7f3215", 15 | "https://deno.land/std@0.111.0/path/_constants.ts": "1247fee4a79b70c89f23499691ef169b41b6ccf01887a0abd131009c5581b853", 16 | "https://deno.land/std@0.111.0/path/_interface.ts": "1fa73b02aaa24867e481a48492b44f2598cd9dfa513c7b34001437007d3642e4", 17 | "https://deno.land/std@0.111.0/path/_util.ts": "2e06a3b9e79beaf62687196bd4b60a4c391d862cfa007a20fc3a39f778ba073b", 18 | "https://deno.land/std@0.111.0/path/common.ts": "f41a38a0719a1e85aa11c6ba3bea5e37c15dd009d705bd8873f94c833568cbc4", 19 | "https://deno.land/std@0.111.0/path/glob.ts": "ea87985765b977cc284b92771003b2070c440e0807c90e1eb0ff3e095911a820", 20 | "https://deno.land/std@0.111.0/path/mod.ts": "4465dc494f271b02569edbb4a18d727063b5dbd6ed84283ff906260970a15d12", 21 | "https://deno.land/std@0.111.0/path/posix.ts": "34349174b9cd121625a2810837a82dd8b986bbaaad5ade690d1de75bbb4555b2", 22 | "https://deno.land/std@0.111.0/path/separator.ts": "8fdcf289b1b76fd726a508f57d3370ca029ae6976fcde5044007f062e643ff1c", 23 | "https://deno.land/std@0.111.0/path/win32.ts": "11549e8c6df8307a8efcfa47ad7b2a75da743eac7d4c89c9723a944661c8bd2e", 24 | "https://deno.land/std@0.111.0/streams/conversion.ts": "fe0059ed9d3c53eda4ba44eb71a6a9acb98c5fdb5ba1b6c6ab28004724c7641b", 25 | "https://deno.land/std@0.128.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74", 26 | "https://deno.land/std@0.128.0/_util/os.ts": "49b92edea1e82ba295ec946de8ffd956ed123e2948d9bd1d3e901b04e4307617", 27 | "https://deno.land/std@0.128.0/fmt/colors.ts": "4575bb20edc666d3ae75fa9fac75f20e4cd423b280008094b05e423cc85047bb", 28 | "https://deno.land/std@0.128.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f", 29 | "https://deno.land/std@0.128.0/fs/empty_dir.ts": "7274d87160de34cbed0531e284df383045cf43543bbeadeb97feac598bd8f3c5", 30 | "https://deno.land/std@0.128.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d", 31 | "https://deno.land/std@0.128.0/fs/expand_glob.ts": "0c10130d67c9b02164b03df8e43c6d6defbf8e395cb69d09e84a8586e6d72ac3", 32 | "https://deno.land/std@0.128.0/fs/walk.ts": "117403ccd21fd322febe56ba06053b1ad5064c802170f19b1ea43214088fe95f", 33 | "https://deno.land/std@0.128.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3", 34 | "https://deno.land/std@0.128.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09", 35 | "https://deno.land/std@0.128.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b", 36 | "https://deno.land/std@0.128.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633", 37 | "https://deno.land/std@0.128.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee", 38 | "https://deno.land/std@0.128.0/path/mod.ts": "4275129bb766f0e475ecc5246aa35689eeade419d72a48355203f31802640be7", 39 | "https://deno.land/std@0.128.0/path/posix.ts": "663e4a6fe30a145f56aa41a22d95114c4c5582d8b57d2d7c9ed27ad2c47636bb", 40 | "https://deno.land/std@0.128.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9", 41 | "https://deno.land/std@0.128.0/path/win32.ts": "e7bdf63e8d9982b4d8a01ef5689425c93310ece950e517476e22af10f41a136e", 42 | "https://deno.land/std@0.172.0/fmt/colors.ts": "938c5d44d889fb82eff6c358bea8baa7e85950a16c9f6dae3ec3a7a729164471", 43 | "https://deno.land/std@0.172.0/testing/_diff.ts": "1a3c044aedf77647d6cac86b798c6417603361b66b54c53331b312caeb447aea", 44 | "https://deno.land/std@0.172.0/testing/_format.ts": "a69126e8a469009adf4cf2a50af889aca364c349797e63174884a52ff75cf4c7", 45 | "https://deno.land/std@0.172.0/testing/asserts.ts": "984ab0bfb3faeed92ffaa3a6b06536c66811185328c5dd146257c702c41b01ab", 46 | "https://deno.land/x/code_block_writer@11.0.0/comment_char.ts": "22b66890bbdf7a2d59777ffd8231710c1fda1c11fadada67632a596937a1a314", 47 | "https://deno.land/x/code_block_writer@11.0.0/mod.ts": "dc43d56c3487bae02886a09754fb09c607da4ea866817e80f3e60632f3391d70", 48 | "https://deno.land/x/code_block_writer@11.0.0/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", 49 | "https://deno.land/x/deno_cache@0.2.1/auth_tokens.ts": "01b94d25abd974153a3111653998b9a43c66d84a0e4b362fc5f4bbbf40a6e0f7", 50 | "https://deno.land/x/deno_cache@0.2.1/cache.ts": "67e301c20161546fea45405316314f4c3d85cc7a367b2fb72042903f308f55b7", 51 | "https://deno.land/x/deno_cache@0.2.1/deno_dir.ts": "e4dc68da5641aa337bcc06fb1df28fcb086b366dcbea7d8aaed7ac7c853fedb1", 52 | "https://deno.land/x/deno_cache@0.2.1/deps.ts": "2ebaba0ad86fff8b9027c6afd4c3909a17cd8bf8c9e263151c980c15c56a18ee", 53 | "https://deno.land/x/deno_cache@0.2.1/dirs.ts": "e07003fabed7112375d4a50040297aae768f9d06bb6c2655ca46880653b576b4", 54 | "https://deno.land/x/deno_cache@0.2.1/disk_cache.ts": "d7a361f0683a032bcca28513a7bbedc28c77cfcc6719e6f6cea156c0ff1108df", 55 | "https://deno.land/x/deno_cache@0.2.1/file_fetcher.ts": "352702994c190c45215f3b8086621e117e88bc2174b020faefb5eca653d71d6a", 56 | "https://deno.land/x/deno_cache@0.2.1/http_cache.ts": "af1500149496e2d0acadec24569e2a9c86a3f600cceef045dcf6f5ce8de72b3a", 57 | "https://deno.land/x/deno_cache@0.2.1/mod.ts": "709ab9d1068be5fd77b020b33e7a9394f1e9b453553b1e2336b72c90283cf3c0", 58 | "https://deno.land/x/deno_cache@0.2.1/util.ts": "652479928551259731686686ff2df6f26bc04e8e4d311137b2bf3bc10f779f48", 59 | "https://deno.land/x/deno_graph@0.6.0/lib/deno_graph.generated.js": "3e1cccd6376d4ad0ea789d66aa0f6b19f737fa8da37b5e6185ef5c269c974f54", 60 | "https://deno.land/x/deno_graph@0.6.0/lib/loader.ts": "13a11c1dea0d85e0ad211be77217b8c06138bbb916afef6f50a04cca415084a9", 61 | "https://deno.land/x/deno_graph@0.6.0/lib/media_type.ts": "36be751aa63d6ae36475b90dca5fae8fd7c3a77cf13684c48cf23a85ee607b31", 62 | "https://deno.land/x/deno_graph@0.6.0/lib/snippets/deno_graph-1c138d6136337537/src/deno_apis.js": "f13f2678d875372cf8489ceb7124623a39fa5bf8de8ee1ec722dbb2ec5ec7845", 63 | "https://deno.land/x/deno_graph@0.6.0/lib/types.d.ts": "68cb232e02a984658b40ffaf6cafb979a06fbfdce7f5bd4c7a83ed1a32a07687", 64 | "https://deno.land/x/deno_graph@0.6.0/mod.ts": "8fe3d39bdcb273adfb41a0bafbbaabec4c6fe6c611b47fed8f46f218edb37e8e", 65 | "https://deno.land/x/dnt@0.22.0/lib/compiler.ts": "9e82c4eebf06e0f948488f148cb5da3ac05480796d5567ef9120800903a8c7f4", 66 | "https://deno.land/x/dnt@0.22.0/lib/compiler_transforms.ts": "316c24175fe6a5d7ac6bb1dd44d14ef8010ea5773a3ac918db4d64f986402d8b", 67 | "https://deno.land/x/dnt@0.22.0/lib/mod.deps.ts": "e499a8363a3d8f909a2334feaf5835dd4ea7bfafd5a7f770ba239e6c0927e7c9", 68 | "https://deno.land/x/dnt@0.22.0/lib/npm_ignore.ts": "36fe32008cd71e995bc08569d2b43e8fba816cbada82fa37d1fe52358d5a2e17", 69 | "https://deno.land/x/dnt@0.22.0/lib/package_json.ts": "ad4a7255ff1f97777a31b163549c2ed90a59c2eaaf19e44c0b3023054bdae8ed", 70 | "https://deno.land/x/dnt@0.22.0/lib/pkg/dnt_wasm.js": "a6a460a97647ab30cff2b22f81bb3701a320cb7d7d18d0fb8c048f7a2db1ac7b", 71 | "https://deno.land/x/dnt@0.22.0/lib/pkg/dnt_wasm_bg.ts": "cbb34c17fd0da4e0e60ab140e72d7faed6ebd240a802b3079e8b7db681d237d4", 72 | "https://deno.land/x/dnt@0.22.0/lib/pkg/snippets/dnt-wasm-a15ef721fa5290c5/helpers.js": "2f623f83602d4fbb30caa63444b10e35b45e9c2b267e49585ec9bb790a4888d8", 73 | "https://deno.land/x/dnt@0.22.0/lib/shims.ts": "4c6b4b1bb48b58d1071e74f0daab29160e02716fe8886f8b5b069291214777d7", 74 | "https://deno.land/x/dnt@0.22.0/lib/test_runner/get_test_runner_code.ts": "5fe5543c8479b5f17c58db4d994de3f3d573e3ca7e4c32c7cf8e338e8e900ba7", 75 | "https://deno.land/x/dnt@0.22.0/lib/test_runner/test_runner.ts": "976920b8c69f26f5316942a66f8957fbc53c105e0cefd9cdef3d2f7c385ec5a2", 76 | "https://deno.land/x/dnt@0.22.0/lib/transform.deps.ts": "2fce3a37e40d40f06faeae5f93322cccdcc8a4a0ee68b27c7022677d882df5be", 77 | "https://deno.land/x/dnt@0.22.0/lib/types.ts": "8506b5ced3921a6ac2a1d5a2bb381bfdbf818c68207f14a1a1fffbf48ee95886", 78 | "https://deno.land/x/dnt@0.22.0/lib/utils.ts": "d2681d634dfa6bd4ad2a32ad15bd419f6f1f895e06c0bf479455fbf1c5f49cd9", 79 | "https://deno.land/x/dnt@0.22.0/mod.ts": "1038999528085c2def6b53a83d50973cf7946598d6d5b64e29457dde4bafcf76", 80 | "https://deno.land/x/dnt@0.22.0/transform.ts": "5829d1b0b03026a07a5630c19a1676defbce8cd09a01f442d362b30176097f7b", 81 | "https://deno.land/x/ts_morph@14.0.0/bootstrap/mod.ts": "b53aad517f106c4079971fcd4a81ab79fadc40b50061a3ab2b741a09119d51e9", 82 | "https://deno.land/x/ts_morph@14.0.0/bootstrap/ts_morph_bootstrap.d.ts": "2be47f54ceb6ef524ed0e2e9f80776d93276a1edadfa2191680927dadd3ccd76", 83 | "https://deno.land/x/ts_morph@14.0.0/bootstrap/ts_morph_bootstrap.js": "7038365181fb388668289e35142ee43881aa053386ca5f86f276edacf42859c7", 84 | "https://deno.land/x/ts_morph@14.0.0/common/DenoRuntime.ts": "9499b723d5e06dc609c170f6ebe239f70535c91ba422720adddc28ef9bd03905", 85 | "https://deno.land/x/ts_morph@14.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed", 86 | "https://deno.land/x/ts_morph@14.0.0/common/ts_morph_common.d.ts": "aacba92e65115e95113ad3c6e652f349434488712a65e18a6642076cfc234235", 87 | "https://deno.land/x/ts_morph@14.0.0/common/ts_morph_common.js": "9f616b75e0decd08f699b9721e886766275ab5e17d766afe55319fb6e6d7037b", 88 | "https://deno.land/x/ts_morph@14.0.0/common/typescript.d.ts": "ba00bb2ada9a5b7e0ab18c7282c0161f5af809112e4439b35c8b3853f7d436a7", 89 | "https://deno.land/x/ts_morph@14.0.0/common/typescript.js": "d6b532a181b94359894da7559663d7430396c8b4a5d8ed436601dc46ba542ee9" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /demo/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/code-frame@^7.0.0": 6 | version "7.18.6" 7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" 8 | integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== 9 | dependencies: 10 | "@babel/highlight" "^7.18.6" 11 | 12 | "@babel/helper-validator-identifier@^7.18.6": 13 | version "7.19.1" 14 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" 15 | integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== 16 | 17 | "@babel/highlight@^7.18.6": 18 | version "7.18.6" 19 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" 20 | integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== 21 | dependencies: 22 | "@babel/helper-validator-identifier" "^7.18.6" 23 | chalk "^2.0.0" 24 | js-tokens "^4.0.0" 25 | 26 | "@jridgewell/gen-mapping@^0.3.0": 27 | version "0.3.2" 28 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" 29 | integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== 30 | dependencies: 31 | "@jridgewell/set-array" "^1.0.1" 32 | "@jridgewell/sourcemap-codec" "^1.4.10" 33 | "@jridgewell/trace-mapping" "^0.3.9" 34 | 35 | "@jridgewell/resolve-uri@3.1.0": 36 | version "3.1.0" 37 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" 38 | integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== 39 | 40 | "@jridgewell/set-array@^1.0.1": 41 | version "1.1.2" 42 | resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" 43 | integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== 44 | 45 | "@jridgewell/source-map@^0.3.2": 46 | version "0.3.2" 47 | resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" 48 | integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== 49 | dependencies: 50 | "@jridgewell/gen-mapping" "^0.3.0" 51 | "@jridgewell/trace-mapping" "^0.3.9" 52 | 53 | "@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10": 54 | version "1.4.14" 55 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" 56 | integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== 57 | 58 | "@jridgewell/trace-mapping@^0.3.9": 59 | version "0.3.17" 60 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985" 61 | integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== 62 | dependencies: 63 | "@jridgewell/resolve-uri" "3.1.0" 64 | "@jridgewell/sourcemap-codec" "1.4.14" 65 | 66 | "@lezer/common@^0.15.0", "@lezer/common@^0.15.7": 67 | version "0.15.12" 68 | resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.12.tgz#2f21aec551dd5fd7d24eb069f90f54d5bc6ee5e9" 69 | integrity sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig== 70 | 71 | "@lezer/lr@^0.15.4": 72 | version "0.15.8" 73 | resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-0.15.8.tgz#1564a911e62b0a0f75ca63794a6aa8c5dc63db21" 74 | integrity sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg== 75 | dependencies: 76 | "@lezer/common" "^0.15.0" 77 | 78 | "@lmdb/lmdb-darwin-arm64@2.5.2": 79 | version "2.5.2" 80 | resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.5.2.tgz#bc66fa43286b5c082e8fee0eacc17995806b6fbe" 81 | integrity sha512-+F8ioQIUN68B4UFiIBYu0QQvgb9FmlKw2ctQMSBfW2QBrZIxz9vD9jCGqTCPqZBRbPHAS/vG1zSXnKqnS2ch/A== 82 | 83 | "@lmdb/lmdb-darwin-x64@2.5.2": 84 | version "2.5.2" 85 | resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.5.2.tgz#89d8390041bce6bab24a82a20392be22faf54ffc" 86 | integrity sha512-KvPH56KRLLx4KSfKBx0m1r7GGGUMXm0jrKmNE7plbHlesZMuPJICtn07HYgQhj1LNsK7Yqwuvnqh1QxhJnF1EA== 87 | 88 | "@lmdb/lmdb-linux-arm64@2.5.2": 89 | version "2.5.2" 90 | resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.5.2.tgz#14fe4c96c2bb1285f93797f45915fa35ee047268" 91 | integrity sha512-aLl89VHL/wjhievEOlPocoefUyWdvzVrcQ/MHQYZm2JfV1jUsrbr/ZfkPPUFvZBf+VSE+Q0clWs9l29PCX1hTQ== 92 | 93 | "@lmdb/lmdb-linux-arm@2.5.2": 94 | version "2.5.2" 95 | resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.5.2.tgz#05bde4573ab10cf21827339fe687148f2590cfa1" 96 | integrity sha512-5kQAP21hAkfW5Bl+e0P57dV4dGYnkNIpR7f/GAh6QHlgXx+vp/teVj4PGRZaKAvt0GX6++N6hF8NnGElLDuIDw== 97 | 98 | "@lmdb/lmdb-linux-x64@2.5.2": 99 | version "2.5.2" 100 | resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.5.2.tgz#d2f85afd857d2c33d2caa5b057944574edafcfee" 101 | integrity sha512-xUdUfwDJLGjOUPH3BuPBt0NlIrR7f/QHKgu3GZIXswMMIihAekj2i97oI0iWG5Bok/b+OBjHPfa8IU9velnP/Q== 102 | 103 | "@lmdb/lmdb-win32-x64@2.5.2": 104 | version "2.5.2" 105 | resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.5.2.tgz#28f643fbc0bec30b07fbe95b137879b6b4d1c9c5" 106 | integrity sha512-zrBczSbXKxEyK2ijtbRdICDygRqWSRPpZMN5dD1T8VMEW5RIhIbwFWw2phDRXuBQdVDpSjalCIUMWMV2h3JaZA== 107 | 108 | "@mischnic/json-sourcemap@^0.1.0": 109 | version "0.1.0" 110 | resolved "https://registry.yarnpkg.com/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz#38af657be4108140a548638267d02a2ea3336507" 111 | integrity sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA== 112 | dependencies: 113 | "@lezer/common" "^0.15.7" 114 | "@lezer/lr" "^0.15.4" 115 | json5 "^2.2.1" 116 | 117 | "@msgpackr-extract/msgpackr-extract-darwin-arm64@2.2.0": 118 | version "2.2.0" 119 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-2.2.0.tgz#901c5937e1441572ea23e631fe6deca68482fe76" 120 | integrity sha512-Z9LFPzfoJi4mflGWV+rv7o7ZbMU5oAU9VmzCgL240KnqDW65Y2HFCT3MW06/ITJSnbVLacmcEJA8phywK7JinQ== 121 | 122 | "@msgpackr-extract/msgpackr-extract-darwin-x64@2.2.0": 123 | version "2.2.0" 124 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.2.0.tgz#fb877fe6bae3c4d3cea29786737840e2ae689066" 125 | integrity sha512-vq0tT8sjZsy4JdSqmadWVw6f66UXqUCabLmUVHZwUFzMgtgoIIQjT4VVRHKvlof3P/dMCkbMJ5hB1oJ9OWHaaw== 126 | 127 | "@msgpackr-extract/msgpackr-extract-linux-arm64@2.2.0": 128 | version "2.2.0" 129 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-2.2.0.tgz#986179c38b10ac41fbdaf7d036c825cbc72855d9" 130 | integrity sha512-hlxxLdRmPyq16QCutUtP8Tm6RDWcyaLsRssaHROatgnkOxdleMTgetf9JsdncL8vLh7FVy/RN9i3XR5dnb9cRA== 131 | 132 | "@msgpackr-extract/msgpackr-extract-linux-arm@2.2.0": 133 | version "2.2.0" 134 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-2.2.0.tgz#15f2c6fe9e0adc06c21af7e95f484ff4880d79ce" 135 | integrity sha512-SaJ3Qq4lX9Syd2xEo9u3qPxi/OB+5JO/ngJKK97XDpa1C587H9EWYO6KD8995DAjSinWvdHKRrCOXVUC5fvGOg== 136 | 137 | "@msgpackr-extract/msgpackr-extract-linux-x64@2.2.0": 138 | version "2.2.0" 139 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-2.2.0.tgz#30cae5c9a202f3e1fa1deb3191b18ffcb2f239a2" 140 | integrity sha512-94y5PJrSOqUNcFKmOl7z319FelCLAE0rz/jPCWS+UtdMZvpa4jrQd+cJPQCLp2Fes1yAW/YUQj/Di6YVT3c3Iw== 141 | 142 | "@msgpackr-extract/msgpackr-extract-win32-x64@2.2.0": 143 | version "2.2.0" 144 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-2.2.0.tgz#016d855b6bc459fd908095811f6826e45dd4ba64" 145 | integrity sha512-XrC0JzsqQSvOyM3t04FMLO6z5gCuhPE6k4FXuLK5xf52ZbdvcFe1yBmo7meCew9B8G2f0T9iu9t3kfTYRYROgA== 146 | 147 | "@parcel/bundler-default@2.8.3": 148 | version "2.8.3" 149 | resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.8.3.tgz#d64739dbc2dbd59d6629861bf77a8083aced5229" 150 | integrity sha512-yJvRsNWWu5fVydsWk3O2L4yIy3UZiKWO2cPDukGOIWMgp/Vbpp+2Ct5IygVRtE22bnseW/E/oe0PV3d2IkEJGg== 151 | dependencies: 152 | "@parcel/diagnostic" "2.8.3" 153 | "@parcel/graph" "2.8.3" 154 | "@parcel/hash" "2.8.3" 155 | "@parcel/plugin" "2.8.3" 156 | "@parcel/utils" "2.8.3" 157 | nullthrows "^1.1.1" 158 | 159 | "@parcel/cache@2.8.3": 160 | version "2.8.3" 161 | resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.8.3.tgz#169e130cf59913c0ed9fadce1a450e68f710e16f" 162 | integrity sha512-k7xv5vSQrJLdXuglo+Hv3yF4BCSs1tQ/8Vbd6CHTkOhf7LcGg6CPtLw053R/KdMpd/4GPn0QrAsOLdATm1ELtQ== 163 | dependencies: 164 | "@parcel/fs" "2.8.3" 165 | "@parcel/logger" "2.8.3" 166 | "@parcel/utils" "2.8.3" 167 | lmdb "2.5.2" 168 | 169 | "@parcel/codeframe@2.8.3": 170 | version "2.8.3" 171 | resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.8.3.tgz#84fb529ef70def7f5bc64f6c59b18d24826f5fcc" 172 | integrity sha512-FE7sY53D6n/+2Pgg6M9iuEC6F5fvmyBkRE4d9VdnOoxhTXtkEqpqYgX7RJ12FAQwNlxKq4suBJQMgQHMF2Kjeg== 173 | dependencies: 174 | chalk "^4.1.0" 175 | 176 | "@parcel/compressor-raw@2.8.3": 177 | version "2.8.3" 178 | resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.8.3.tgz#301753df8c6de967553149639e8a4179b88f0c95" 179 | integrity sha512-bVDsqleBUxRdKMakWSlWC9ZjOcqDKE60BE+Gh3JSN6WJrycJ02P5wxjTVF4CStNP/G7X17U+nkENxSlMG77ySg== 180 | dependencies: 181 | "@parcel/plugin" "2.8.3" 182 | 183 | "@parcel/config-default@2.8.3": 184 | version "2.8.3" 185 | resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.8.3.tgz#9a43486e7c702e96c68052c37b79098d7240e35b" 186 | integrity sha512-o/A/mbrO6X/BfGS65Sib8d6SSG45NYrNooNBkH/o7zbOBSRQxwyTlysleK1/3Wa35YpvFyLOwgfakqCtbGy4fw== 187 | dependencies: 188 | "@parcel/bundler-default" "2.8.3" 189 | "@parcel/compressor-raw" "2.8.3" 190 | "@parcel/namer-default" "2.8.3" 191 | "@parcel/optimizer-css" "2.8.3" 192 | "@parcel/optimizer-htmlnano" "2.8.3" 193 | "@parcel/optimizer-image" "2.8.3" 194 | "@parcel/optimizer-svgo" "2.8.3" 195 | "@parcel/optimizer-terser" "2.8.3" 196 | "@parcel/packager-css" "2.8.3" 197 | "@parcel/packager-html" "2.8.3" 198 | "@parcel/packager-js" "2.8.3" 199 | "@parcel/packager-raw" "2.8.3" 200 | "@parcel/packager-svg" "2.8.3" 201 | "@parcel/reporter-dev-server" "2.8.3" 202 | "@parcel/resolver-default" "2.8.3" 203 | "@parcel/runtime-browser-hmr" "2.8.3" 204 | "@parcel/runtime-js" "2.8.3" 205 | "@parcel/runtime-react-refresh" "2.8.3" 206 | "@parcel/runtime-service-worker" "2.8.3" 207 | "@parcel/transformer-babel" "2.8.3" 208 | "@parcel/transformer-css" "2.8.3" 209 | "@parcel/transformer-html" "2.8.3" 210 | "@parcel/transformer-image" "2.8.3" 211 | "@parcel/transformer-js" "2.8.3" 212 | "@parcel/transformer-json" "2.8.3" 213 | "@parcel/transformer-postcss" "2.8.3" 214 | "@parcel/transformer-posthtml" "2.8.3" 215 | "@parcel/transformer-raw" "2.8.3" 216 | "@parcel/transformer-react-refresh-wrap" "2.8.3" 217 | "@parcel/transformer-svg" "2.8.3" 218 | 219 | "@parcel/core@2.8.3": 220 | version "2.8.3" 221 | resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.8.3.tgz#22a69f36095d53736ab10bf42697d9aa5f4e382b" 222 | integrity sha512-Euf/un4ZAiClnlUXqPB9phQlKbveU+2CotZv7m7i+qkgvFn5nAGnrV4h1OzQU42j9dpgOxWi7AttUDMrvkbhCQ== 223 | dependencies: 224 | "@mischnic/json-sourcemap" "^0.1.0" 225 | "@parcel/cache" "2.8.3" 226 | "@parcel/diagnostic" "2.8.3" 227 | "@parcel/events" "2.8.3" 228 | "@parcel/fs" "2.8.3" 229 | "@parcel/graph" "2.8.3" 230 | "@parcel/hash" "2.8.3" 231 | "@parcel/logger" "2.8.3" 232 | "@parcel/package-manager" "2.8.3" 233 | "@parcel/plugin" "2.8.3" 234 | "@parcel/source-map" "^2.1.1" 235 | "@parcel/types" "2.8.3" 236 | "@parcel/utils" "2.8.3" 237 | "@parcel/workers" "2.8.3" 238 | abortcontroller-polyfill "^1.1.9" 239 | base-x "^3.0.8" 240 | browserslist "^4.6.6" 241 | clone "^2.1.1" 242 | dotenv "^7.0.0" 243 | dotenv-expand "^5.1.0" 244 | json5 "^2.2.0" 245 | msgpackr "^1.5.4" 246 | nullthrows "^1.1.1" 247 | semver "^5.7.1" 248 | 249 | "@parcel/diagnostic@2.8.3": 250 | version "2.8.3" 251 | resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.8.3.tgz#d560276d5d2804b48beafa1feaf3fc6b2ac5e39d" 252 | integrity sha512-u7wSzuMhLGWZjVNYJZq/SOViS3uFG0xwIcqXw12w54Uozd6BH8JlhVtVyAsq9kqnn7YFkw6pXHqAo5Tzh4FqsQ== 253 | dependencies: 254 | "@mischnic/json-sourcemap" "^0.1.0" 255 | nullthrows "^1.1.1" 256 | 257 | "@parcel/events@2.8.3": 258 | version "2.8.3" 259 | resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.8.3.tgz#205f8d874e6ecc2cbdb941bf8d54bae669e571af" 260 | integrity sha512-hoIS4tAxWp8FJk3628bsgKxEvR7bq2scCVYHSqZ4fTi/s0+VymEATrRCUqf+12e5H47uw1/ZjoqrGtBI02pz4w== 261 | 262 | "@parcel/fs-search@2.8.3": 263 | version "2.8.3" 264 | resolved "https://registry.yarnpkg.com/@parcel/fs-search/-/fs-search-2.8.3.tgz#1c7d812c110b808758f44c56e61dfffdb09e9451" 265 | integrity sha512-DJBT2N8knfN7Na6PP2mett3spQLTqxFrvl0gv+TJRp61T8Ljc4VuUTb0hqBj+belaASIp3Q+e8+SgaFQu7wLiQ== 266 | dependencies: 267 | detect-libc "^1.0.3" 268 | 269 | "@parcel/fs@2.8.3": 270 | version "2.8.3" 271 | resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.8.3.tgz#80536afe877fc8a2bd26be5576b9ba27bb4c5754" 272 | integrity sha512-y+i+oXbT7lP0e0pJZi/YSm1vg0LDsbycFuHZIL80pNwdEppUAtibfJZCp606B7HOjMAlNZOBo48e3hPG3d8jgQ== 273 | dependencies: 274 | "@parcel/fs-search" "2.8.3" 275 | "@parcel/types" "2.8.3" 276 | "@parcel/utils" "2.8.3" 277 | "@parcel/watcher" "^2.0.7" 278 | "@parcel/workers" "2.8.3" 279 | 280 | "@parcel/graph@2.8.3": 281 | version "2.8.3" 282 | resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.8.3.tgz#00ffe8ec032e74fee57199e54529f1da7322571d" 283 | integrity sha512-26GL8fYZPdsRhSXCZ0ZWliloK6DHlMJPWh6Z+3VVZ5mnDSbYg/rRKWmrkhnr99ZWmL9rJsv4G74ZwvDEXTMPBg== 284 | dependencies: 285 | nullthrows "^1.1.1" 286 | 287 | "@parcel/hash@2.8.3": 288 | version "2.8.3" 289 | resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.8.3.tgz#bc2499a27395169616cad2a99e19e69b9098f6e9" 290 | integrity sha512-FVItqzjWmnyP4ZsVgX+G00+6U2IzOvqDtdwQIWisCcVoXJFCqZJDy6oa2qDDFz96xCCCynjRjPdQx2jYBCpfYw== 291 | dependencies: 292 | detect-libc "^1.0.3" 293 | xxhash-wasm "^0.4.2" 294 | 295 | "@parcel/logger@2.8.3": 296 | version "2.8.3" 297 | resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.8.3.tgz#e14e4debafb3ca9e87c07c06780f9afc38b2712c" 298 | integrity sha512-Kpxd3O/Vs7nYJIzkdmB6Bvp3l/85ydIxaZaPfGSGTYOfaffSOTkhcW9l6WemsxUrlts4za6CaEWcc4DOvaMOPA== 299 | dependencies: 300 | "@parcel/diagnostic" "2.8.3" 301 | "@parcel/events" "2.8.3" 302 | 303 | "@parcel/markdown-ansi@2.8.3": 304 | version "2.8.3" 305 | resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.8.3.tgz#1337d421bb1133ad178f386a8e1b746631bba4a1" 306 | integrity sha512-4v+pjyoh9f5zuU/gJlNvNFGEAb6J90sOBwpKJYJhdWXLZMNFCVzSigxrYO+vCsi8G4rl6/B2c0LcwIMjGPHmFQ== 307 | dependencies: 308 | chalk "^4.1.0" 309 | 310 | "@parcel/namer-default@2.8.3": 311 | version "2.8.3" 312 | resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.8.3.tgz#5304bee74beb4b9c1880781bdbe35be0656372f4" 313 | integrity sha512-tJ7JehZviS5QwnxbARd8Uh63rkikZdZs1QOyivUhEvhN+DddSAVEdQLHGPzkl3YRk0tjFhbqo+Jci7TpezuAMw== 314 | dependencies: 315 | "@parcel/diagnostic" "2.8.3" 316 | "@parcel/plugin" "2.8.3" 317 | nullthrows "^1.1.1" 318 | 319 | "@parcel/node-resolver-core@2.8.3": 320 | version "2.8.3" 321 | resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.8.3.tgz#581df074a27646400b3fed9da95297b616a7db8f" 322 | integrity sha512-12YryWcA5Iw2WNoEVr/t2HDjYR1iEzbjEcxfh1vaVDdZ020PiGw67g5hyIE/tsnG7SRJ0xdRx1fQ2hDgED+0Ww== 323 | dependencies: 324 | "@parcel/diagnostic" "2.8.3" 325 | "@parcel/utils" "2.8.3" 326 | nullthrows "^1.1.1" 327 | semver "^5.7.1" 328 | 329 | "@parcel/optimizer-css@2.8.3": 330 | version "2.8.3" 331 | resolved "https://registry.yarnpkg.com/@parcel/optimizer-css/-/optimizer-css-2.8.3.tgz#420a333f4b78f7ff15e69217dfed34421b1143ee" 332 | integrity sha512-JotGAWo8JhuXsQDK0UkzeQB0UR5hDAKvAviXrjqB4KM9wZNLhLleeEAW4Hk8R9smCeQFP6Xg/N/NkLDpqMwT3g== 333 | dependencies: 334 | "@parcel/diagnostic" "2.8.3" 335 | "@parcel/plugin" "2.8.3" 336 | "@parcel/source-map" "^2.1.1" 337 | "@parcel/utils" "2.8.3" 338 | browserslist "^4.6.6" 339 | lightningcss "^1.16.1" 340 | nullthrows "^1.1.1" 341 | 342 | "@parcel/optimizer-htmlnano@2.8.3": 343 | version "2.8.3" 344 | resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.8.3.tgz#a71ab6f0f24160ef9f573266064438eff65e96d0" 345 | integrity sha512-L8/fHbEy8Id2a2E0fwR5eKGlv9VYDjrH9PwdJE9Za9v1O/vEsfl/0T/79/x129l5O0yB6EFQkFa20MiK3b+vOg== 346 | dependencies: 347 | "@parcel/plugin" "2.8.3" 348 | htmlnano "^2.0.0" 349 | nullthrows "^1.1.1" 350 | posthtml "^0.16.5" 351 | svgo "^2.4.0" 352 | 353 | "@parcel/optimizer-image@2.8.3": 354 | version "2.8.3" 355 | resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.8.3.tgz#ea49b4245b4f7d60b38c7585c6311fb21d341baa" 356 | integrity sha512-SD71sSH27SkCDNUNx9A3jizqB/WIJr3dsfp+JZGZC42tpD/Siim6Rqy9M4To/BpMMQIIiEXa5ofwS+DgTEiEHQ== 357 | dependencies: 358 | "@parcel/diagnostic" "2.8.3" 359 | "@parcel/plugin" "2.8.3" 360 | "@parcel/utils" "2.8.3" 361 | "@parcel/workers" "2.8.3" 362 | detect-libc "^1.0.3" 363 | 364 | "@parcel/optimizer-svgo@2.8.3": 365 | version "2.8.3" 366 | resolved "https://registry.yarnpkg.com/@parcel/optimizer-svgo/-/optimizer-svgo-2.8.3.tgz#04da4efec6b623679539a84961bff6998034ba8a" 367 | integrity sha512-9KQed99NZnQw3/W4qBYVQ7212rzA9EqrQG019TIWJzkA9tjGBMIm2c/nXpK1tc3hQ3e7KkXkFCQ3C+ibVUnHNA== 368 | dependencies: 369 | "@parcel/diagnostic" "2.8.3" 370 | "@parcel/plugin" "2.8.3" 371 | "@parcel/utils" "2.8.3" 372 | svgo "^2.4.0" 373 | 374 | "@parcel/optimizer-terser@2.8.3": 375 | version "2.8.3" 376 | resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.8.3.tgz#3a06d98d09386a1a0ae1be85376a8739bfba9618" 377 | integrity sha512-9EeQlN6zIeUWwzrzu6Q2pQSaYsYGah8MtiQ/hog9KEPlYTP60hBv/+utDyYEHSQhL7y5ym08tPX5GzBvwAD/dA== 378 | dependencies: 379 | "@parcel/diagnostic" "2.8.3" 380 | "@parcel/plugin" "2.8.3" 381 | "@parcel/source-map" "^2.1.1" 382 | "@parcel/utils" "2.8.3" 383 | nullthrows "^1.1.1" 384 | terser "^5.2.0" 385 | 386 | "@parcel/package-manager@2.8.3": 387 | version "2.8.3" 388 | resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.8.3.tgz#ddd0d62feae3cf0fb6cc0537791b3a16296ad458" 389 | integrity sha512-tIpY5pD2lH53p9hpi++GsODy6V3khSTX4pLEGuMpeSYbHthnOViobqIlFLsjni+QA1pfc8NNNIQwSNdGjYflVA== 390 | dependencies: 391 | "@parcel/diagnostic" "2.8.3" 392 | "@parcel/fs" "2.8.3" 393 | "@parcel/logger" "2.8.3" 394 | "@parcel/types" "2.8.3" 395 | "@parcel/utils" "2.8.3" 396 | "@parcel/workers" "2.8.3" 397 | semver "^5.7.1" 398 | 399 | "@parcel/packager-css@2.8.3": 400 | version "2.8.3" 401 | resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.8.3.tgz#0eff34268cb4f5dfb53c1bbca85f5567aeb1835a" 402 | integrity sha512-WyvkMmsurlHG8d8oUVm7S+D+cC/T3qGeqogb7sTI52gB6uiywU7lRCizLNqGFyFGIxcVTVHWnSHqItBcLN76lA== 403 | dependencies: 404 | "@parcel/plugin" "2.8.3" 405 | "@parcel/source-map" "^2.1.1" 406 | "@parcel/utils" "2.8.3" 407 | nullthrows "^1.1.1" 408 | 409 | "@parcel/packager-html@2.8.3": 410 | version "2.8.3" 411 | resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.8.3.tgz#f9263b891aa4dd46c6e2fa2b07025a482132fff1" 412 | integrity sha512-OhPu1Hx1RRKJodpiu86ZqL8el2Aa4uhBHF6RAL1Pcrh2EhRRlPf70Sk0tC22zUpYL7es+iNKZ/n0Rl+OWSHWEw== 413 | dependencies: 414 | "@parcel/plugin" "2.8.3" 415 | "@parcel/types" "2.8.3" 416 | "@parcel/utils" "2.8.3" 417 | nullthrows "^1.1.1" 418 | posthtml "^0.16.5" 419 | 420 | "@parcel/packager-js@2.8.3": 421 | version "2.8.3" 422 | resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.8.3.tgz#3ed11565915d73d12192b6901c75a6b820e4a83a" 423 | integrity sha512-0pGKC3Ax5vFuxuZCRB+nBucRfFRz4ioie19BbDxYnvBxrd4M3FIu45njf6zbBYsI9eXqaDnL1b3DcZJfYqtIzw== 424 | dependencies: 425 | "@parcel/diagnostic" "2.8.3" 426 | "@parcel/hash" "2.8.3" 427 | "@parcel/plugin" "2.8.3" 428 | "@parcel/source-map" "^2.1.1" 429 | "@parcel/utils" "2.8.3" 430 | globals "^13.2.0" 431 | nullthrows "^1.1.1" 432 | 433 | "@parcel/packager-raw@2.8.3": 434 | version "2.8.3" 435 | resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.8.3.tgz#bdec826df991e186cb58691cc45d12ad5c06676e" 436 | integrity sha512-BA6enNQo1RCnco9MhkxGrjOk59O71IZ9DPKu3lCtqqYEVd823tXff2clDKHK25i6cChmeHu6oB1Rb73hlPqhUA== 437 | dependencies: 438 | "@parcel/plugin" "2.8.3" 439 | 440 | "@parcel/packager-svg@2.8.3": 441 | version "2.8.3" 442 | resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.8.3.tgz#7233315296001c531cb55ca96b5f2ef672343630" 443 | integrity sha512-mvIoHpmv5yzl36OjrklTDFShLUfPFTwrmp1eIwiszGdEBuQaX7JVI3Oo2jbVQgcN4W7J6SENzGQ3Q5hPTW3pMw== 444 | dependencies: 445 | "@parcel/plugin" "2.8.3" 446 | "@parcel/types" "2.8.3" 447 | "@parcel/utils" "2.8.3" 448 | posthtml "^0.16.4" 449 | 450 | "@parcel/plugin@2.8.3": 451 | version "2.8.3" 452 | resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.8.3.tgz#7bb30a5775eaa6473c27f002a0a3ee7308d6d669" 453 | integrity sha512-jZ6mnsS4D9X9GaNnvrixDQwlUQJCohDX2hGyM0U0bY2NWU8Km97SjtoCpWjq+XBCx/gpC4g58+fk9VQeZq2vlw== 454 | dependencies: 455 | "@parcel/types" "2.8.3" 456 | 457 | "@parcel/reporter-cli@2.8.3": 458 | version "2.8.3" 459 | resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.8.3.tgz#12a4743b51b8fe6837f53c20e01bbf1f7336e8e4" 460 | integrity sha512-3sJkS6tFFzgIOz3u3IpD/RsmRxvOKKiQHOTkiiqRt1l44mMDGKS7zANRnJYsQzdCsgwc9SOP30XFgJwtoVlMbw== 461 | dependencies: 462 | "@parcel/plugin" "2.8.3" 463 | "@parcel/types" "2.8.3" 464 | "@parcel/utils" "2.8.3" 465 | chalk "^4.1.0" 466 | term-size "^2.2.1" 467 | 468 | "@parcel/reporter-dev-server@2.8.3": 469 | version "2.8.3" 470 | resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.8.3.tgz#a0daa5cc015642684cea561f4e0e7116bbffdc1c" 471 | integrity sha512-Y8C8hzgzTd13IoWTj+COYXEyCkXfmVJs3//GDBsH22pbtSFMuzAZd+8J9qsCo0EWpiDow7V9f1LischvEh3FbQ== 472 | dependencies: 473 | "@parcel/plugin" "2.8.3" 474 | "@parcel/utils" "2.8.3" 475 | 476 | "@parcel/resolver-default@2.8.3": 477 | version "2.8.3" 478 | resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.8.3.tgz#5ae41e537ae4a793c1abb47f094482b9e2ac3535" 479 | integrity sha512-k0B5M/PJ+3rFbNj4xZSBr6d6HVIe6DH/P3dClLcgBYSXAvElNDfXgtIimbjCyItFkW9/BfcgOVKEEIZOeySH/A== 480 | dependencies: 481 | "@parcel/node-resolver-core" "2.8.3" 482 | "@parcel/plugin" "2.8.3" 483 | 484 | "@parcel/runtime-browser-hmr@2.8.3": 485 | version "2.8.3" 486 | resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.8.3.tgz#1fa74e1fbd1030b0a920c58afa3a9eb7dc4bcd1e" 487 | integrity sha512-2O1PYi2j/Q0lTyGNV3JdBYwg4rKo6TEVFlYGdd5wCYU9ZIN9RRuoCnWWH2qCPj3pjIVtBeppYxzfVjPEHINWVg== 488 | dependencies: 489 | "@parcel/plugin" "2.8.3" 490 | "@parcel/utils" "2.8.3" 491 | 492 | "@parcel/runtime-js@2.8.3": 493 | version "2.8.3" 494 | resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.8.3.tgz#0baa4c8fbf77eabce05d01ccc186614968ffc0cd" 495 | integrity sha512-IRja0vNKwvMtPgIqkBQh0QtRn0XcxNC8HU1jrgWGRckzu10qJWO+5ULgtOeR4pv9krffmMPqywGXw6l/gvJKYQ== 496 | dependencies: 497 | "@parcel/plugin" "2.8.3" 498 | "@parcel/utils" "2.8.3" 499 | nullthrows "^1.1.1" 500 | 501 | "@parcel/runtime-react-refresh@2.8.3": 502 | version "2.8.3" 503 | resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.8.3.tgz#381a942fb81e8f5ac6c7e0ee1b91dbf34763c3f8" 504 | integrity sha512-2v/qFKp00MfG0234OdOgQNAo6TLENpFYZMbVbAsPMY9ITiqG73MrEsrGXVoGbYiGTMB/Toer/lSWlJxtacOCuA== 505 | dependencies: 506 | "@parcel/plugin" "2.8.3" 507 | "@parcel/utils" "2.8.3" 508 | react-error-overlay "6.0.9" 509 | react-refresh "^0.9.0" 510 | 511 | "@parcel/runtime-service-worker@2.8.3": 512 | version "2.8.3" 513 | resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.8.3.tgz#54d92da9ff1dfbd27db0e84164a22fa59e99b348" 514 | integrity sha512-/Skkw+EeRiwzOJso5fQtK8c9b452uWLNhQH1ISTodbmlcyB4YalAiSsyHCtMYD0c3/t5Sx4ZS7vxBAtQd0RvOw== 515 | dependencies: 516 | "@parcel/plugin" "2.8.3" 517 | "@parcel/utils" "2.8.3" 518 | nullthrows "^1.1.1" 519 | 520 | "@parcel/source-map@^2.1.1": 521 | version "2.1.1" 522 | resolved "https://registry.yarnpkg.com/@parcel/source-map/-/source-map-2.1.1.tgz#fb193b82dba6dd62cc7a76b326f57bb35000a782" 523 | integrity sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew== 524 | dependencies: 525 | detect-libc "^1.0.3" 526 | 527 | "@parcel/transformer-babel@2.8.3": 528 | version "2.8.3" 529 | resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.8.3.tgz#286bc6cb9afe4c0259f0b28e0f2f47322a24b130" 530 | integrity sha512-L6lExfpvvC7T/g3pxf3CIJRouQl+sgrSzuWQ0fD4PemUDHvHchSP4SNUVnd6gOytF3Y1KpnEZIunQGi5xVqQCQ== 531 | dependencies: 532 | "@parcel/diagnostic" "2.8.3" 533 | "@parcel/plugin" "2.8.3" 534 | "@parcel/source-map" "^2.1.1" 535 | "@parcel/utils" "2.8.3" 536 | browserslist "^4.6.6" 537 | json5 "^2.2.0" 538 | nullthrows "^1.1.1" 539 | semver "^5.7.0" 540 | 541 | "@parcel/transformer-css@2.8.3": 542 | version "2.8.3" 543 | resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.8.3.tgz#d6c44100204e73841ad8e0f90472172ea8b9120c" 544 | integrity sha512-xTqFwlSXtnaYen9ivAgz+xPW7yRl/u4QxtnDyDpz5dr8gSeOpQYRcjkd4RsYzKsWzZcGtB5EofEk8ayUbWKEUg== 545 | dependencies: 546 | "@parcel/diagnostic" "2.8.3" 547 | "@parcel/plugin" "2.8.3" 548 | "@parcel/source-map" "^2.1.1" 549 | "@parcel/utils" "2.8.3" 550 | browserslist "^4.6.6" 551 | lightningcss "^1.16.1" 552 | nullthrows "^1.1.1" 553 | 554 | "@parcel/transformer-html@2.8.3": 555 | version "2.8.3" 556 | resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.8.3.tgz#5c68b28ee6b8c7a13b8aee87f7957ad3227bd83f" 557 | integrity sha512-kIZO3qsMYTbSnSpl9cnZog+SwL517ffWH54JeB410OSAYF1ouf4n5v9qBnALZbuCCmPwJRGs4jUtE452hxwN4g== 558 | dependencies: 559 | "@parcel/diagnostic" "2.8.3" 560 | "@parcel/hash" "2.8.3" 561 | "@parcel/plugin" "2.8.3" 562 | nullthrows "^1.1.1" 563 | posthtml "^0.16.5" 564 | posthtml-parser "^0.10.1" 565 | posthtml-render "^3.0.0" 566 | semver "^5.7.1" 567 | srcset "4" 568 | 569 | "@parcel/transformer-image@2.8.3": 570 | version "2.8.3" 571 | resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.8.3.tgz#73805b2bfc3c8919d7737544e5f8be39e3f303fe" 572 | integrity sha512-cO4uptcCGTi5H6bvTrAWEFUsTNhA4kCo8BSvRSCHA2sf/4C5tGQPHt3JhdO0GQLPwZRCh/R41EkJs5HZ8A8DAg== 573 | dependencies: 574 | "@parcel/plugin" "2.8.3" 575 | "@parcel/utils" "2.8.3" 576 | "@parcel/workers" "2.8.3" 577 | nullthrows "^1.1.1" 578 | 579 | "@parcel/transformer-js@2.8.3": 580 | version "2.8.3" 581 | resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.8.3.tgz#fe400df428394d1e7fe5afb6dea5c7c858e44f03" 582 | integrity sha512-9Qd6bib+sWRcpovvzvxwy/PdFrLUXGfmSW9XcVVG8pvgXsZPFaNjnNT8stzGQj1pQiougCoxMY4aTM5p1lGHEQ== 583 | dependencies: 584 | "@parcel/diagnostic" "2.8.3" 585 | "@parcel/plugin" "2.8.3" 586 | "@parcel/source-map" "^2.1.1" 587 | "@parcel/utils" "2.8.3" 588 | "@parcel/workers" "2.8.3" 589 | "@swc/helpers" "^0.4.12" 590 | browserslist "^4.6.6" 591 | detect-libc "^1.0.3" 592 | nullthrows "^1.1.1" 593 | regenerator-runtime "^0.13.7" 594 | semver "^5.7.1" 595 | 596 | "@parcel/transformer-json@2.8.3": 597 | version "2.8.3" 598 | resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.8.3.tgz#25deb3a5138cc70a83269fc5d39d564609354d36" 599 | integrity sha512-B7LmVq5Q7bZO4ERb6NHtRuUKWGysEeaj9H4zelnyBv+wLgpo4f5FCxSE1/rTNmP9u1qHvQ3scGdK6EdSSokGPg== 600 | dependencies: 601 | "@parcel/plugin" "2.8.3" 602 | json5 "^2.2.0" 603 | 604 | "@parcel/transformer-postcss@2.8.3": 605 | version "2.8.3" 606 | resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.8.3.tgz#df4fdc1c90893823445f2a8eb8e2bdd0349ccc58" 607 | integrity sha512-e8luB/poIlz6jBsD1Izms+6ElbyzuoFVa4lFVLZnTAChI3UxPdt9p/uTsIO46HyBps/Bk8ocvt3J4YF84jzmvg== 608 | dependencies: 609 | "@parcel/diagnostic" "2.8.3" 610 | "@parcel/hash" "2.8.3" 611 | "@parcel/plugin" "2.8.3" 612 | "@parcel/utils" "2.8.3" 613 | clone "^2.1.1" 614 | nullthrows "^1.1.1" 615 | postcss-value-parser "^4.2.0" 616 | semver "^5.7.1" 617 | 618 | "@parcel/transformer-posthtml@2.8.3": 619 | version "2.8.3" 620 | resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.8.3.tgz#7c3912a5a631cb26485f6464e0d6eeabb6f1e718" 621 | integrity sha512-pkzf9Smyeaw4uaRLsT41RGrPLT5Aip8ZPcntawAfIo+KivBQUV0erY1IvHYjyfFzq1ld/Fo2Ith9He6mxpPifA== 622 | dependencies: 623 | "@parcel/plugin" "2.8.3" 624 | "@parcel/utils" "2.8.3" 625 | nullthrows "^1.1.1" 626 | posthtml "^0.16.5" 627 | posthtml-parser "^0.10.1" 628 | posthtml-render "^3.0.0" 629 | semver "^5.7.1" 630 | 631 | "@parcel/transformer-raw@2.8.3": 632 | version "2.8.3" 633 | resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.8.3.tgz#3a22213fe18a5f83fd78889cb49f06e059cfead7" 634 | integrity sha512-G+5cXnd2/1O3nV/pgRxVKZY/HcGSseuhAe71gQdSQftb8uJEURyUHoQ9Eh0JUD3MgWh9V+nIKoyFEZdf9T0sUQ== 635 | dependencies: 636 | "@parcel/plugin" "2.8.3" 637 | 638 | "@parcel/transformer-react-refresh-wrap@2.8.3": 639 | version "2.8.3" 640 | resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.8.3.tgz#8b0392638405dd470a886002229f7889d5464822" 641 | integrity sha512-q8AAoEvBnCf/nPvgOwFwKZfEl/thwq7c2duxXkhl+tTLDRN2vGmyz4355IxCkavSX+pLWSQ5MexklSEeMkgthg== 642 | dependencies: 643 | "@parcel/plugin" "2.8.3" 644 | "@parcel/utils" "2.8.3" 645 | react-refresh "^0.9.0" 646 | 647 | "@parcel/transformer-svg@2.8.3": 648 | version "2.8.3" 649 | resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.8.3.tgz#4df959cba4ebf45d7aaddd540f752e6e84df38b2" 650 | integrity sha512-3Zr/gBzxi1ZH1fftH/+KsZU7w5GqkmxlB0ZM8ovS5E/Pl1lq1t0xvGJue9m2VuQqP8Mxfpl5qLFmsKlhaZdMIQ== 651 | dependencies: 652 | "@parcel/diagnostic" "2.8.3" 653 | "@parcel/hash" "2.8.3" 654 | "@parcel/plugin" "2.8.3" 655 | nullthrows "^1.1.1" 656 | posthtml "^0.16.5" 657 | posthtml-parser "^0.10.1" 658 | posthtml-render "^3.0.0" 659 | semver "^5.7.1" 660 | 661 | "@parcel/types@2.8.3": 662 | version "2.8.3" 663 | resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.8.3.tgz#3306bc5391b6913bd619914894b8cd84a24b30fa" 664 | integrity sha512-FECA1FB7+0UpITKU0D6TgGBpGxYpVSMNEENZbSJxFSajNy3wrko+zwBKQmFOLOiPcEtnGikxNs+jkFWbPlUAtw== 665 | dependencies: 666 | "@parcel/cache" "2.8.3" 667 | "@parcel/diagnostic" "2.8.3" 668 | "@parcel/fs" "2.8.3" 669 | "@parcel/package-manager" "2.8.3" 670 | "@parcel/source-map" "^2.1.1" 671 | "@parcel/workers" "2.8.3" 672 | utility-types "^3.10.0" 673 | 674 | "@parcel/utils@2.8.3": 675 | version "2.8.3" 676 | resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.8.3.tgz#0d56c9e8e22c119590a5e044a0e01031965da40e" 677 | integrity sha512-IhVrmNiJ+LOKHcCivG5dnuLGjhPYxQ/IzbnF2DKNQXWBTsYlHkJZpmz7THoeLtLliGmSOZ3ZCsbR8/tJJKmxjA== 678 | dependencies: 679 | "@parcel/codeframe" "2.8.3" 680 | "@parcel/diagnostic" "2.8.3" 681 | "@parcel/hash" "2.8.3" 682 | "@parcel/logger" "2.8.3" 683 | "@parcel/markdown-ansi" "2.8.3" 684 | "@parcel/source-map" "^2.1.1" 685 | chalk "^4.1.0" 686 | 687 | "@parcel/watcher@^2.0.7": 688 | version "2.1.0" 689 | resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.1.0.tgz#5f32969362db4893922c526a842d8af7a8538545" 690 | integrity sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw== 691 | dependencies: 692 | is-glob "^4.0.3" 693 | micromatch "^4.0.5" 694 | node-addon-api "^3.2.1" 695 | node-gyp-build "^4.3.0" 696 | 697 | "@parcel/workers@2.8.3": 698 | version "2.8.3" 699 | resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.8.3.tgz#255450ccf4db234082407e4ddda5fd575f08c235" 700 | integrity sha512-+AxBnKgjqVpUHBcHLWIHcjYgKIvHIpZjN33mG5LG9XXvrZiqdWvouEzqEXlVLq5VzzVbKIQQcmsvRy138YErkg== 701 | dependencies: 702 | "@parcel/diagnostic" "2.8.3" 703 | "@parcel/logger" "2.8.3" 704 | "@parcel/types" "2.8.3" 705 | "@parcel/utils" "2.8.3" 706 | chrome-trace-event "^1.0.2" 707 | nullthrows "^1.1.1" 708 | 709 | "@preact/signals-core@^1.2.3": 710 | version "1.2.3" 711 | resolved "https://registry.yarnpkg.com/@preact/signals-core/-/signals-core-1.2.3.tgz#36bf8b31027a5b9ade9e50cf9fba80614e8a2354" 712 | integrity sha512-Kui4p7PMcEQevBgsTO0JBo3gyQ88Q3qzEvsVCuSp11t0JcN4DmGCTJcGRVSCq7Bn7lGxJBO+57jNSzDoDJ+QmA== 713 | 714 | "@preact/signals@^1.1.3": 715 | version "1.1.3" 716 | resolved "https://registry.yarnpkg.com/@preact/signals/-/signals-1.1.3.tgz#8226d3001cc162c568225083275ed8d595f8bf1f" 717 | integrity sha512-N09DuAVvc90bBZVRwD+aFhtGyHAmJLhS3IFoawO/bYJRcil4k83nBOchpCEoS0s5+BXBpahgp0Mjf+IOqP57Og== 718 | dependencies: 719 | "@preact/signals-core" "^1.2.3" 720 | 721 | "@swc/helpers@^0.4.12": 722 | version "0.4.14" 723 | resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.14.tgz#1352ac6d95e3617ccb7c1498ff019654f1e12a74" 724 | integrity sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw== 725 | dependencies: 726 | tslib "^2.4.0" 727 | 728 | "@trysound/sax@0.2.0": 729 | version "0.2.0" 730 | resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" 731 | integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== 732 | 733 | "@types/parse-json@^4.0.0": 734 | version "4.0.0" 735 | resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" 736 | integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== 737 | 738 | abortcontroller-polyfill@^1.1.9: 739 | version "1.7.5" 740 | resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz#6738495f4e901fbb57b6c0611d0c75f76c485bed" 741 | integrity sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ== 742 | 743 | acorn@^8.5.0: 744 | version "8.8.1" 745 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" 746 | integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== 747 | 748 | ansi-styles@^3.2.1: 749 | version "3.2.1" 750 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 751 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 752 | dependencies: 753 | color-convert "^1.9.0" 754 | 755 | ansi-styles@^4.1.0: 756 | version "4.3.0" 757 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 758 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 759 | dependencies: 760 | color-convert "^2.0.1" 761 | 762 | base-x@^3.0.8: 763 | version "3.0.9" 764 | resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" 765 | integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== 766 | dependencies: 767 | safe-buffer "^5.0.1" 768 | 769 | boolbase@^1.0.0: 770 | version "1.0.0" 771 | resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" 772 | integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== 773 | 774 | braces@^3.0.2: 775 | version "3.0.2" 776 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 777 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 778 | dependencies: 779 | fill-range "^7.0.1" 780 | 781 | browserslist@^4.6.6: 782 | version "4.21.4" 783 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" 784 | integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== 785 | dependencies: 786 | caniuse-lite "^1.0.30001400" 787 | electron-to-chromium "^1.4.251" 788 | node-releases "^2.0.6" 789 | update-browserslist-db "^1.0.9" 790 | 791 | buffer-from@^1.0.0: 792 | version "1.1.2" 793 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 794 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 795 | 796 | callsites@^3.0.0: 797 | version "3.1.0" 798 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" 799 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 800 | 801 | caniuse-lite@^1.0.30001400: 802 | version "1.0.30001446" 803 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001446.tgz#6d4ba828ab19f49f9bcd14a8430d30feebf1e0c5" 804 | integrity sha512-fEoga4PrImGcwUUGEol/PoFCSBnSkA9drgdkxXkJLsUBOnJ8rs3zDv6ApqYXGQFOyMPsjh79naWhF4DAxbF8rw== 805 | 806 | chalk@^2.0.0: 807 | version "2.4.2" 808 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 809 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 810 | dependencies: 811 | ansi-styles "^3.2.1" 812 | escape-string-regexp "^1.0.5" 813 | supports-color "^5.3.0" 814 | 815 | chalk@^4.1.0: 816 | version "4.1.2" 817 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 818 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 819 | dependencies: 820 | ansi-styles "^4.1.0" 821 | supports-color "^7.1.0" 822 | 823 | chrome-trace-event@^1.0.2: 824 | version "1.0.3" 825 | resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" 826 | integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== 827 | 828 | clone@^2.1.1: 829 | version "2.1.2" 830 | resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" 831 | integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== 832 | 833 | color-convert@^1.9.0: 834 | version "1.9.3" 835 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 836 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 837 | dependencies: 838 | color-name "1.1.3" 839 | 840 | color-convert@^2.0.1: 841 | version "2.0.1" 842 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 843 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 844 | dependencies: 845 | color-name "~1.1.4" 846 | 847 | color-name@1.1.3: 848 | version "1.1.3" 849 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 850 | integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== 851 | 852 | color-name@~1.1.4: 853 | version "1.1.4" 854 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 855 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 856 | 857 | commander@^2.20.0: 858 | version "2.20.3" 859 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 860 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 861 | 862 | commander@^7.0.0, commander@^7.2.0: 863 | version "7.2.0" 864 | resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" 865 | integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== 866 | 867 | cosmiconfig@^7.0.1: 868 | version "7.1.0" 869 | resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" 870 | integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== 871 | dependencies: 872 | "@types/parse-json" "^4.0.0" 873 | import-fresh "^3.2.1" 874 | parse-json "^5.0.0" 875 | path-type "^4.0.0" 876 | yaml "^1.10.0" 877 | 878 | css-select@^4.1.3: 879 | version "4.3.0" 880 | resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" 881 | integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== 882 | dependencies: 883 | boolbase "^1.0.0" 884 | css-what "^6.0.1" 885 | domhandler "^4.3.1" 886 | domutils "^2.8.0" 887 | nth-check "^2.0.1" 888 | 889 | css-tree@^1.1.2, css-tree@^1.1.3: 890 | version "1.1.3" 891 | resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" 892 | integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== 893 | dependencies: 894 | mdn-data "2.0.14" 895 | source-map "^0.6.1" 896 | 897 | css-what@^6.0.1: 898 | version "6.1.0" 899 | resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" 900 | integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== 901 | 902 | csso@^4.2.0: 903 | version "4.2.0" 904 | resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" 905 | integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== 906 | dependencies: 907 | css-tree "^1.1.2" 908 | 909 | detect-libc@^1.0.3: 910 | version "1.0.3" 911 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" 912 | integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== 913 | 914 | dom-serializer@^1.0.1: 915 | version "1.4.1" 916 | resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" 917 | integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== 918 | dependencies: 919 | domelementtype "^2.0.1" 920 | domhandler "^4.2.0" 921 | entities "^2.0.0" 922 | 923 | domelementtype@^2.0.1, domelementtype@^2.2.0: 924 | version "2.3.0" 925 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" 926 | integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== 927 | 928 | domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1: 929 | version "4.3.1" 930 | resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" 931 | integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== 932 | dependencies: 933 | domelementtype "^2.2.0" 934 | 935 | domutils@^2.8.0: 936 | version "2.8.0" 937 | resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" 938 | integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== 939 | dependencies: 940 | dom-serializer "^1.0.1" 941 | domelementtype "^2.2.0" 942 | domhandler "^4.2.0" 943 | 944 | dotenv-expand@^5.1.0: 945 | version "5.1.0" 946 | resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" 947 | integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== 948 | 949 | dotenv@^7.0.0: 950 | version "7.0.0" 951 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" 952 | integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== 953 | 954 | electron-to-chromium@^1.4.251: 955 | version "1.4.284" 956 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592" 957 | integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA== 958 | 959 | entities@^2.0.0: 960 | version "2.2.0" 961 | resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" 962 | integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== 963 | 964 | entities@^3.0.1: 965 | version "3.0.1" 966 | resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" 967 | integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== 968 | 969 | error-ex@^1.3.1: 970 | version "1.3.2" 971 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" 972 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== 973 | dependencies: 974 | is-arrayish "^0.2.1" 975 | 976 | escalade@^3.1.1: 977 | version "3.1.1" 978 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 979 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 980 | 981 | escape-string-regexp@^1.0.5: 982 | version "1.0.5" 983 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 984 | integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== 985 | 986 | fill-range@^7.0.1: 987 | version "7.0.1" 988 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 989 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 990 | dependencies: 991 | to-regex-range "^5.0.1" 992 | 993 | get-port@^4.2.0: 994 | version "4.2.0" 995 | resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" 996 | integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== 997 | 998 | globals@^13.2.0: 999 | version "13.19.0" 1000 | resolved "https://registry.yarnpkg.com/globals/-/globals-13.19.0.tgz#7a42de8e6ad4f7242fbcca27ea5b23aca367b5c8" 1001 | integrity sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ== 1002 | dependencies: 1003 | type-fest "^0.20.2" 1004 | 1005 | has-flag@^3.0.0: 1006 | version "3.0.0" 1007 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 1008 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 1009 | 1010 | has-flag@^4.0.0: 1011 | version "4.0.0" 1012 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 1013 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 1014 | 1015 | htmlnano@^2.0.0: 1016 | version "2.0.3" 1017 | resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-2.0.3.tgz#50ee639ed63357d4a6c01309f52a35892e4edc2e" 1018 | integrity sha512-S4PGGj9RbdgW8LhbILNK7W9JhmYP8zmDY7KDV/8eCiJBQJlbmltp5I0gv8c5ntLljfdxxfmJ+UJVSqyH4mb41A== 1019 | dependencies: 1020 | cosmiconfig "^7.0.1" 1021 | posthtml "^0.16.5" 1022 | timsort "^0.3.0" 1023 | 1024 | htmlparser2@^7.1.1: 1025 | version "7.2.0" 1026 | resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5" 1027 | integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog== 1028 | dependencies: 1029 | domelementtype "^2.0.1" 1030 | domhandler "^4.2.2" 1031 | domutils "^2.8.0" 1032 | entities "^3.0.1" 1033 | 1034 | import-fresh@^3.2.1: 1035 | version "3.3.0" 1036 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" 1037 | integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== 1038 | dependencies: 1039 | parent-module "^1.0.0" 1040 | resolve-from "^4.0.0" 1041 | 1042 | is-arrayish@^0.2.1: 1043 | version "0.2.1" 1044 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 1045 | integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== 1046 | 1047 | is-extglob@^2.1.1: 1048 | version "2.1.1" 1049 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 1050 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 1051 | 1052 | is-glob@^4.0.3: 1053 | version "4.0.3" 1054 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 1055 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 1056 | dependencies: 1057 | is-extglob "^2.1.1" 1058 | 1059 | is-json@^2.0.1: 1060 | version "2.0.1" 1061 | resolved "https://registry.yarnpkg.com/is-json/-/is-json-2.0.1.tgz#6be166d144828a131d686891b983df62c39491ff" 1062 | integrity sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA== 1063 | 1064 | is-number@^7.0.0: 1065 | version "7.0.0" 1066 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 1067 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 1068 | 1069 | js-tokens@^4.0.0: 1070 | version "4.0.0" 1071 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 1072 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 1073 | 1074 | json-parse-even-better-errors@^2.3.0: 1075 | version "2.3.1" 1076 | resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" 1077 | integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== 1078 | 1079 | json5@^2.2.0, json5@^2.2.1: 1080 | version "2.2.3" 1081 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" 1082 | integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== 1083 | 1084 | lightningcss-darwin-arm64@1.18.0: 1085 | version "1.18.0" 1086 | resolved "https://registry.yarnpkg.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.18.0.tgz#bcd7d494d99c69947abd71136a42e80dfa80c682" 1087 | integrity sha512-OqjydwtiNPgdH1ByIjA1YzqvDG/OMR6L3LPN6wRl1729LB0y4Mik7L06kmZaTb+pvUHr+NmDd2KCwnlrQ4zO3w== 1088 | 1089 | lightningcss-darwin-x64@1.18.0: 1090 | version "1.18.0" 1091 | resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.18.0.tgz#952abea2405fe2bb8dd0bb57a9d5590f8d1d6414" 1092 | integrity sha512-mNiuPHj89/JHZmJMp+5H8EZSt6EL5DZRWJ31O6k3DrLLnRIQjXuXdDdN8kP7LoIkeWI5xvyD60CsReJm+YWYAw== 1093 | 1094 | lightningcss-linux-arm-gnueabihf@1.18.0: 1095 | version "1.18.0" 1096 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.18.0.tgz#23ca85e05dc4def9b4975aef307554ef292b56cd" 1097 | integrity sha512-S+25JjI6601HiAVoTDXW6SqH+E94a+FHA7WQqseyNHunOgVWKcAkNEc2LJvVxgwTq6z41sDIb9/M3Z9wa9lk4A== 1098 | 1099 | lightningcss-linux-arm64-gnu@1.18.0: 1100 | version "1.18.0" 1101 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.18.0.tgz#6c8e0a6e2c8b44cf180f3a0f0740402e8f656155" 1102 | integrity sha512-JSqh4+21dCgBecIQUet35dtE4PhhSEMyqe3y0ZNQrAJQ5kyUPSQHiw81WXnPJcOSTTpG0TyMLiC8K//+BsFGQA== 1103 | 1104 | lightningcss-linux-arm64-musl@1.18.0: 1105 | version "1.18.0" 1106 | resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.18.0.tgz#88393c101cf236ea0cdc97fddd66b82db964d835" 1107 | integrity sha512-2FWHa8iUhShnZnqhn2wfIcK5adJat9hAAaX7etNsoXJymlliDIOFuBQEsba2KBAZSM4QqfQtvRdR7m8i0I7ybQ== 1108 | 1109 | lightningcss-linux-x64-gnu@1.18.0: 1110 | version "1.18.0" 1111 | resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.18.0.tgz#ad068d24836568337bfe545650565e13f813c8ee" 1112 | integrity sha512-plCPGQJtDZHcLVKVRLnQVF2XRsIC32WvuJhQ7fJ7F6BV98b/VZX0OlX05qUaOESD9dCDHjYSfxsgcvOKgCWh7A== 1113 | 1114 | lightningcss-linux-x64-musl@1.18.0: 1115 | version "1.18.0" 1116 | resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.18.0.tgz#4d84de26b8185aa42450e0f4c83bbfb5a36ae750" 1117 | integrity sha512-na+BGtVU6fpZvOHKhnlA0XHeibkT3/46nj6vLluG3kzdJYoBKU6dIl7DSOk++8jv4ybZyFJ0aOFMMSc8g2h58A== 1118 | 1119 | lightningcss-win32-x64-msvc@1.18.0: 1120 | version "1.18.0" 1121 | resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.18.0.tgz#f83952d16b83dfce65f4615f87c867769220d117" 1122 | integrity sha512-5qeAH4RMNy2yMNEl7e5TI6upt/7xD2ZpHWH4RkT8iJ7/6POS5mjHbXWUO9Q1hhDhqkdzGa76uAdMzEouIeCyNw== 1123 | 1124 | lightningcss@^1.16.1: 1125 | version "1.18.0" 1126 | resolved "https://registry.yarnpkg.com/lightningcss/-/lightningcss-1.18.0.tgz#ca3327a1a7571a83bbb9733ed4e4cded775bdadf" 1127 | integrity sha512-uk10tNxi5fhZqU93vtYiQgx/8a9f0Kvtj5AXIm+VlOXY+t/DWDmCZWJEkZJmmALgvbS6aAW8or+Kq85eJ6TDTw== 1128 | dependencies: 1129 | detect-libc "^1.0.3" 1130 | optionalDependencies: 1131 | lightningcss-darwin-arm64 "1.18.0" 1132 | lightningcss-darwin-x64 "1.18.0" 1133 | lightningcss-linux-arm-gnueabihf "1.18.0" 1134 | lightningcss-linux-arm64-gnu "1.18.0" 1135 | lightningcss-linux-arm64-musl "1.18.0" 1136 | lightningcss-linux-x64-gnu "1.18.0" 1137 | lightningcss-linux-x64-musl "1.18.0" 1138 | lightningcss-win32-x64-msvc "1.18.0" 1139 | 1140 | lines-and-columns@^1.1.6: 1141 | version "1.2.4" 1142 | resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" 1143 | integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== 1144 | 1145 | lmdb@2.5.2: 1146 | version "2.5.2" 1147 | resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.5.2.tgz#37e28a9fb43405f4dc48c44cec0e13a14c4a6ff1" 1148 | integrity sha512-V5V5Xa2Hp9i2XsbDALkBTeHXnBXh/lEmk9p22zdr7jtuOIY9TGhjK6vAvTpOOx9IKU4hJkRWZxn/HsvR1ELLtA== 1149 | dependencies: 1150 | msgpackr "^1.5.4" 1151 | node-addon-api "^4.3.0" 1152 | node-gyp-build-optional-packages "5.0.3" 1153 | ordered-binary "^1.2.4" 1154 | weak-lru-cache "^1.2.2" 1155 | optionalDependencies: 1156 | "@lmdb/lmdb-darwin-arm64" "2.5.2" 1157 | "@lmdb/lmdb-darwin-x64" "2.5.2" 1158 | "@lmdb/lmdb-linux-arm" "2.5.2" 1159 | "@lmdb/lmdb-linux-arm64" "2.5.2" 1160 | "@lmdb/lmdb-linux-x64" "2.5.2" 1161 | "@lmdb/lmdb-win32-x64" "2.5.2" 1162 | 1163 | mdn-data@2.0.14: 1164 | version "2.0.14" 1165 | resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" 1166 | integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== 1167 | 1168 | micromatch@^4.0.5: 1169 | version "4.0.5" 1170 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" 1171 | integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== 1172 | dependencies: 1173 | braces "^3.0.2" 1174 | picomatch "^2.3.1" 1175 | 1176 | msgpackr-extract@^2.2.0: 1177 | version "2.2.0" 1178 | resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-2.2.0.tgz#4bb749b58d9764cfdc0d91c7977a007b08e8f262" 1179 | integrity sha512-0YcvWSv7ZOGl9Od6Y5iJ3XnPww8O7WLcpYMDwX+PAA/uXLDtyw94PJv9GLQV/nnp3cWlDhMoyKZIQLrx33sWog== 1180 | dependencies: 1181 | node-gyp-build-optional-packages "5.0.3" 1182 | optionalDependencies: 1183 | "@msgpackr-extract/msgpackr-extract-darwin-arm64" "2.2.0" 1184 | "@msgpackr-extract/msgpackr-extract-darwin-x64" "2.2.0" 1185 | "@msgpackr-extract/msgpackr-extract-linux-arm" "2.2.0" 1186 | "@msgpackr-extract/msgpackr-extract-linux-arm64" "2.2.0" 1187 | "@msgpackr-extract/msgpackr-extract-linux-x64" "2.2.0" 1188 | "@msgpackr-extract/msgpackr-extract-win32-x64" "2.2.0" 1189 | 1190 | msgpackr@^1.5.4: 1191 | version "1.8.1" 1192 | resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.8.1.tgz#2298aed8a14f83e99df77d344cbda3e436f29b5b" 1193 | integrity sha512-05fT4J8ZqjYlR4QcRDIhLCYKUOHXk7C/xa62GzMKj74l3up9k2QZ3LgFc6qWdsPHl91QA2WLWqWc8b8t7GLNNw== 1194 | optionalDependencies: 1195 | msgpackr-extract "^2.2.0" 1196 | 1197 | node-addon-api@^3.2.1: 1198 | version "3.2.1" 1199 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" 1200 | integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== 1201 | 1202 | node-addon-api@^4.3.0: 1203 | version "4.3.0" 1204 | resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" 1205 | integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== 1206 | 1207 | node-gyp-build-optional-packages@5.0.3: 1208 | version "5.0.3" 1209 | resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" 1210 | integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== 1211 | 1212 | node-gyp-build@^4.3.0: 1213 | version "4.6.0" 1214 | resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" 1215 | integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== 1216 | 1217 | node-releases@^2.0.6: 1218 | version "2.0.8" 1219 | resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.8.tgz#0f349cdc8fcfa39a92ac0be9bc48b7706292b9ae" 1220 | integrity sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A== 1221 | 1222 | nth-check@^2.0.1: 1223 | version "2.1.1" 1224 | resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" 1225 | integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== 1226 | dependencies: 1227 | boolbase "^1.0.0" 1228 | 1229 | nullthrows@^1.1.1: 1230 | version "1.1.1" 1231 | resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" 1232 | integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== 1233 | 1234 | ordered-binary@^1.2.4: 1235 | version "1.4.0" 1236 | resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.4.0.tgz#6bb53d44925f3b8afc33d1eed0fa15693b211389" 1237 | integrity sha512-EHQ/jk4/a9hLupIKxTfUsQRej1Yd/0QLQs3vGvIqg5ZtCYSzNhkzHoZc7Zf4e4kUlDaC3Uw8Q/1opOLNN2OKRQ== 1238 | 1239 | parcel@^2.8.3: 1240 | version "2.8.3" 1241 | resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.8.3.tgz#1ff71d7317274fd367379bc7310a52c6b75d30c2" 1242 | integrity sha512-5rMBpbNE72g6jZvkdR5gS2nyhwIXaJy8i65osOqs/+5b7zgf3eMKgjSsDrv6bhz3gzifsba6MBJiZdBckl+vnA== 1243 | dependencies: 1244 | "@parcel/config-default" "2.8.3" 1245 | "@parcel/core" "2.8.3" 1246 | "@parcel/diagnostic" "2.8.3" 1247 | "@parcel/events" "2.8.3" 1248 | "@parcel/fs" "2.8.3" 1249 | "@parcel/logger" "2.8.3" 1250 | "@parcel/package-manager" "2.8.3" 1251 | "@parcel/reporter-cli" "2.8.3" 1252 | "@parcel/reporter-dev-server" "2.8.3" 1253 | "@parcel/utils" "2.8.3" 1254 | chalk "^4.1.0" 1255 | commander "^7.0.0" 1256 | get-port "^4.2.0" 1257 | v8-compile-cache "^2.0.0" 1258 | 1259 | parent-module@^1.0.0: 1260 | version "1.0.1" 1261 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" 1262 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 1263 | dependencies: 1264 | callsites "^3.0.0" 1265 | 1266 | parse-json@^5.0.0: 1267 | version "5.2.0" 1268 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" 1269 | integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== 1270 | dependencies: 1271 | "@babel/code-frame" "^7.0.0" 1272 | error-ex "^1.3.1" 1273 | json-parse-even-better-errors "^2.3.0" 1274 | lines-and-columns "^1.1.6" 1275 | 1276 | path-type@^4.0.0: 1277 | version "4.0.0" 1278 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 1279 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 1280 | 1281 | picocolors@^1.0.0: 1282 | version "1.0.0" 1283 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 1284 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 1285 | 1286 | picomatch@^2.3.1: 1287 | version "2.3.1" 1288 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 1289 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 1290 | 1291 | postcss-value-parser@^4.2.0: 1292 | version "4.2.0" 1293 | resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" 1294 | integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== 1295 | 1296 | posthtml-parser@^0.10.1: 1297 | version "0.10.2" 1298 | resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.10.2.tgz#df364d7b179f2a6bf0466b56be7b98fd4e97c573" 1299 | integrity sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg== 1300 | dependencies: 1301 | htmlparser2 "^7.1.1" 1302 | 1303 | posthtml-parser@^0.11.0: 1304 | version "0.11.0" 1305 | resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.11.0.tgz#25d1c7bf811ea83559bc4c21c189a29747a24b7a" 1306 | integrity sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw== 1307 | dependencies: 1308 | htmlparser2 "^7.1.1" 1309 | 1310 | posthtml-render@^3.0.0: 1311 | version "3.0.0" 1312 | resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-3.0.0.tgz#97be44931496f495b4f07b99e903cc70ad6a3205" 1313 | integrity sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA== 1314 | dependencies: 1315 | is-json "^2.0.1" 1316 | 1317 | posthtml@^0.16.4, posthtml@^0.16.5: 1318 | version "0.16.6" 1319 | resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.16.6.tgz#e2fc407f67a64d2fa3567afe770409ffdadafe59" 1320 | integrity sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ== 1321 | dependencies: 1322 | posthtml-parser "^0.11.0" 1323 | posthtml-render "^3.0.0" 1324 | 1325 | preact-render-to-string@^5.1.2: 1326 | version "5.2.6" 1327 | resolved "https://registry.yarnpkg.com/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz#0ff0c86cd118d30affb825193f18e92bd59d0604" 1328 | integrity sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw== 1329 | dependencies: 1330 | pretty-format "^3.8.0" 1331 | 1332 | preact@^10.1.0: 1333 | version "10.11.3" 1334 | resolved "https://registry.yarnpkg.com/preact/-/preact-10.11.3.tgz#8a7e4ba19d3992c488b0785afcc0f8aa13c78d19" 1335 | integrity sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg== 1336 | 1337 | pretty-format@^3.8.0: 1338 | version "3.8.0" 1339 | resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385" 1340 | integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew== 1341 | 1342 | react-error-overlay@6.0.9: 1343 | version "6.0.9" 1344 | resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" 1345 | integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== 1346 | 1347 | react-refresh@^0.9.0: 1348 | version "0.9.0" 1349 | resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" 1350 | integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== 1351 | 1352 | regenerator-runtime@^0.13.7: 1353 | version "0.13.11" 1354 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" 1355 | integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== 1356 | 1357 | resolve-from@^4.0.0: 1358 | version "4.0.0" 1359 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 1360 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 1361 | 1362 | safe-buffer@^5.0.1: 1363 | version "5.2.1" 1364 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 1365 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 1366 | 1367 | semver@^5.7.0, semver@^5.7.1: 1368 | version "5.7.1" 1369 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 1370 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 1371 | 1372 | source-map-support@~0.5.20: 1373 | version "0.5.21" 1374 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 1375 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 1376 | dependencies: 1377 | buffer-from "^1.0.0" 1378 | source-map "^0.6.0" 1379 | 1380 | source-map@^0.6.0, source-map@^0.6.1: 1381 | version "0.6.1" 1382 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 1383 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 1384 | 1385 | srcset@4: 1386 | version "4.0.0" 1387 | resolved "https://registry.yarnpkg.com/srcset/-/srcset-4.0.0.tgz#336816b665b14cd013ba545b6fe62357f86e65f4" 1388 | integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw== 1389 | 1390 | stable@^0.1.8: 1391 | version "0.1.8" 1392 | resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" 1393 | integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== 1394 | 1395 | supports-color@^5.3.0: 1396 | version "5.5.0" 1397 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1398 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1399 | dependencies: 1400 | has-flag "^3.0.0" 1401 | 1402 | supports-color@^7.1.0: 1403 | version "7.2.0" 1404 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1405 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1406 | dependencies: 1407 | has-flag "^4.0.0" 1408 | 1409 | svgo@^2.4.0: 1410 | version "2.8.0" 1411 | resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" 1412 | integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== 1413 | dependencies: 1414 | "@trysound/sax" "0.2.0" 1415 | commander "^7.2.0" 1416 | css-select "^4.1.3" 1417 | css-tree "^1.1.3" 1418 | csso "^4.2.0" 1419 | picocolors "^1.0.0" 1420 | stable "^0.1.8" 1421 | 1422 | term-size@^2.2.1: 1423 | version "2.2.1" 1424 | resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" 1425 | integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== 1426 | 1427 | terser@^5.2.0: 1428 | version "5.16.1" 1429 | resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.1.tgz#5af3bc3d0f24241c7fb2024199d5c461a1075880" 1430 | integrity sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw== 1431 | dependencies: 1432 | "@jridgewell/source-map" "^0.3.2" 1433 | acorn "^8.5.0" 1434 | commander "^2.20.0" 1435 | source-map-support "~0.5.20" 1436 | 1437 | timsort@^0.3.0: 1438 | version "0.3.0" 1439 | resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" 1440 | integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== 1441 | 1442 | to-regex-range@^5.0.1: 1443 | version "5.0.1" 1444 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1445 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1446 | dependencies: 1447 | is-number "^7.0.0" 1448 | 1449 | tslib@^2.4.0: 1450 | version "2.4.1" 1451 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" 1452 | integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== 1453 | 1454 | type-fest@^0.20.2: 1455 | version "0.20.2" 1456 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" 1457 | integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== 1458 | 1459 | update-browserslist-db@^1.0.9: 1460 | version "1.0.10" 1461 | resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3" 1462 | integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== 1463 | dependencies: 1464 | escalade "^3.1.1" 1465 | picocolors "^1.0.0" 1466 | 1467 | utility-types@^3.10.0: 1468 | version "3.10.0" 1469 | resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" 1470 | integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== 1471 | 1472 | v8-compile-cache@^2.0.0: 1473 | version "2.3.0" 1474 | resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" 1475 | integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== 1476 | 1477 | weak-lru-cache@^1.2.2: 1478 | version "1.2.2" 1479 | resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz#fdbb6741f36bae9540d12f480ce8254060dccd19" 1480 | integrity sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw== 1481 | 1482 | xxhash-wasm@^0.4.2: 1483 | version "0.4.2" 1484 | resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz#752398c131a4dd407b5132ba62ad372029be6f79" 1485 | integrity sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA== 1486 | 1487 | yaml@^1.10.0: 1488 | version "1.10.2" 1489 | resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" 1490 | integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== 1491 | --------------------------------------------------------------------------------