diff --git a/.gitignore b/.gitignore index 053db1d..8ee1d68 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,9 @@ coverage/ *.pem *.key +# Compiled binary (built by make build-bin) +bin/quak + # quak runtime data (in case anyone runs the CLI from inside the repo) .quak/ diff --git a/Makefile b/Makefile index e053861..6ea39a8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: test lint fmt fmt-check check build dev clean docker hooks +.PHONY: test lint fmt fmt-check check build build-bin dev clean docker hooks # Use `timeout` (GNU coreutils) when available so `make test` is hard-capped. # On macOS without coreutils this is empty and the cap is skipped. @@ -26,6 +26,9 @@ check: test lint fmt-check build: @$(YARN) tsc +build-bin: + nix-shell -p bun --run "bun build bin/quak.ts --compile --outfile bin/quak" + dev: @$(YARN) tsc --watch diff --git a/bin/quak.ts b/bin/quak.ts index 6726791..09a24e4 100644 --- a/bin/quak.ts +++ b/bin/quak.ts @@ -477,4 +477,5 @@ helper process.exit(results.some((r) => !r.success) ? 1 : 0); }); +await init(); program.parse(); diff --git a/package.json b/package.json index 7d2b90d..00baf6b 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@eslint/js": "9.38.0", "@types/libsodium-wrappers-sumo": "0.8.2", "@types/node": "22.18.13", - "@types/sharp": "0.32.0", "eslint": "9.38.0", "prettier": "3.8.1", "typescript": "5.9.3", @@ -43,7 +42,7 @@ "env-paths": "4.0.0", "exif-reader": "2.0.3", "fast-srp-hap": "2.0.4", - "libsodium-wrappers-sumo": "0.8.4", - "sharp": "0.34.5" + "jpeg-js": "0.4.4", + "libsodium-wrappers-sumo": "0.8.4" } } diff --git a/src/metadata-backup.ts b/src/metadata-backup.ts index aacf990..f227ddf 100644 --- a/src/metadata-backup.ts +++ b/src/metadata-backup.ts @@ -1,8 +1,14 @@ import { gunzipSync } from "node:zlib"; -import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { + mkdirSync, + mkdtempSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import sharp from "sharp"; +import * as jpeg from "jpeg-js"; import exifReader from "exif-reader"; import type { Client } from "./client.js"; import { decryptBlob, fromBase64 } from "./crypto/index.js"; @@ -62,6 +68,87 @@ const fetchMLDataForFiles = async ( return result; }; +// Extract the raw EXIF APP1 segment from JPEG bytes. Returns the EXIF +// data buffer (starting after the APP1 length field, at the "Exif\0\0" +// header) or undefined if no APP1 marker is found. +const extractExifFromJpeg = (buf: Uint8Array): Buffer | undefined => { + if (buf[0] !== 0xff || buf[1] !== 0xd8) return undefined; + let offset = 2; + while (offset < buf.length - 1) { + if (buf[offset] !== 0xff) return undefined; + const marker = buf[offset + 1]!; + if (marker === 0xda) break; // start of scan, no more markers + if (offset + 3 >= buf.length) break; + const len = (buf[offset + 2]! << 8) | buf[offset + 3]!; + if (marker === 0xe1) { + // APP1 — check for "Exif\0\0" header + if ( + buf[offset + 4] === 0x45 && + buf[offset + 5] === 0x78 && + buf[offset + 6] === 0x69 && + buf[offset + 7] === 0x66 + ) { + return Buffer.from( + buf.buffer, + buf.byteOffset + offset + 4, + len - 2, + ); + } + } + offset += 2 + len; + } + return undefined; +}; + +const extractImageMetadata = ( + fileBytes: Uint8Array, +): Record | undefined => { + try { + const result: Record = {}; + + // Try to get dimensions from JPEG decode + try { + const decoded = jpeg.decode(fileBytes, { + useTArray: true, + formatAsRGBA: false, + }); + result.format = "jpeg"; + result.width = decoded.width; + result.height = decoded.height; + } catch { + // Not a JPEG or corrupt; still try EXIF extraction + } + + const exifBuf = extractExifFromJpeg(fileBytes); + if (exifBuf) { + try { + result.exif = exifReader(exifBuf); + } catch { + result.exifRaw = exifBuf.toString("base64"); + } + } + + // Extract XMP (look for "http://ns.adobe.com/xap" in the bytes) + const xmpStart = Buffer.from(fileBytes).indexOf("", xmpEnd); + result.xmp = Buffer.from(fileBytes) + .subarray(xmpStart, end !== -1 ? end + 2 : xmpEnd + 50) + .toString("utf-8"); + } + } + + return Object.keys(result).length > 0 ? result : undefined; + } catch { + return undefined; + } +}; + const extractExif = async ( client: Client, file: EnteFile, @@ -70,42 +157,8 @@ const extractExif = async ( try { const origPath = join(tmpDir, "original"); await client.downloadFile(file, origPath); - const meta = await sharp(origPath).metadata(); - - const result: Record = { - format: meta.format, - width: meta.width, - height: meta.height, - space: meta.space, - channels: meta.channels, - depth: meta.depth, - density: meta.density, - chromaSubSampling: meta.chromaSubSampling, - isProgressive: meta.isProgressive, - hasProfile: meta.hasProfile, - hasAlpha: meta.hasAlpha, - orientation: meta.orientation, - }; - - if (meta.exif) { - try { - result.exif = exifReader(meta.exif); - } catch { - // Malformed EXIF; store the raw bytes as base64 instead - result.exifRaw = meta.exif.toString("base64"); - } - } - if (meta.iptc) result.iptcRaw = meta.iptc.toString("base64"); - if (meta.xmp) { - try { - result.xmp = Buffer.from(meta.xmp).toString("utf-8"); - } catch { - result.xmpRaw = meta.xmp.toString("base64"); - } - } - if (meta.icc) result.iccRaw = meta.icc.toString("base64"); - - return result; + const fileBytes = new Uint8Array(readFileSync(origPath)); + return extractImageMetadata(fileBytes); } catch { return undefined; } finally { @@ -182,7 +235,6 @@ export const runMetadataBackup = async ( ); log(`Got ML data for ${mlDataMap.size} file(s)`); - // Write per-file JSON (with optional ML data and EXIF) const writtenFileIDs = new Set(); for (const { file, colDirName } of allFiles) { const colDir = join(outDir, "collections", colDirName); diff --git a/src/thumbnails.ts b/src/thumbnails.ts index efdc84d..4176f5e 100644 --- a/src/thumbnails.ts +++ b/src/thumbnails.ts @@ -1,5 +1,6 @@ import { createHash } from "node:crypto"; -import sharp from "sharp"; +import { readFileSync } from "node:fs"; +import * as jpeg from "jpeg-js"; import type { Client } from "./client.js"; import { encryptBlob, toBase64 } from "./crypto/index.js"; import { downloadFile } from "./download/index.js"; @@ -74,16 +75,71 @@ export const listMissingThumbnails = async ( return missing; }; -const generateThumbnail = async (originalPath: string): Promise => { - const result = await sharp(originalPath) - .rotate() - .resize(THUMB_MAX_DIMENSION, THUMB_MAX_DIMENSION, { - fit: "inside", - withoutEnlargement: true, - }) - .jpeg({ quality: THUMB_JPEG_QUALITY }) - .toBuffer(); - return new Uint8Array(result); +// Bilinear resize of RGBA pixel buffer +const resizeRGBA = ( + src: Uint8Array, + srcW: number, + srcH: number, + dstW: number, + dstH: number, +): Uint8Array => { + const dst = new Uint8Array(dstW * dstH * 4); + const xRatio = srcW / dstW; + const yRatio = srcH / dstH; + for (let y = 0; y < dstH; y++) { + const srcY = y * yRatio; + const y0 = Math.floor(srcY); + const y1 = Math.min(y0 + 1, srcH - 1); + const fy = srcY - y0; + for (let x = 0; x < dstW; x++) { + const srcX = x * xRatio; + const x0 = Math.floor(srcX); + const x1 = Math.min(x0 + 1, srcW - 1); + const fx = srcX - x0; + const i00 = (y0 * srcW + x0) * 4; + const i10 = (y0 * srcW + x1) * 4; + const i01 = (y1 * srcW + x0) * 4; + const i11 = (y1 * srcW + x1) * 4; + const di = (y * dstW + x) * 4; + for (let c = 0; c < 4; c++) { + dst[di + c] = Math.round( + src[i00 + c]! * (1 - fx) * (1 - fy) + + src[i10 + c]! * fx * (1 - fy) + + src[i01 + c]! * (1 - fx) * fy + + src[i11 + c]! * fx * fy, + ); + } + } + } + return dst; +}; + +const generateThumbnail = (fileBytes: Uint8Array): Uint8Array => { + const decoded = jpeg.decode(fileBytes, { + useTArray: true, + formatAsRGBA: true, + }); + const { width: srcW, height: srcH } = decoded; + const scale = Math.min( + THUMB_MAX_DIMENSION / srcW, + THUMB_MAX_DIMENSION / srcH, + 1, + ); + const dstW = Math.round(srcW * scale); + const dstH = Math.round(srcH * scale); + + let pixels: Uint8Array; + if (scale < 1) { + pixels = resizeRGBA(decoded.data, srcW, srcH, dstW, dstH); + } else { + pixels = decoded.data; + } + + const encoded = jpeg.encode( + { data: pixels, width: dstW, height: dstH }, + THUMB_JPEG_QUALITY, + ); + return new Uint8Array(encoded.data); }; export const fixMissingThumbnails = async ( @@ -139,7 +195,8 @@ export const fixMissingThumbnails = async ( log( `[${collectionName}] Generating thumbnail for ${file.metadata.title}...`, ); - const thumbJpeg = await generateThumbnail(origPath); + const fileBytes = readFileSync(origPath); + const thumbJpeg = generateThumbnail(new Uint8Array(fileBytes)); log( `[${collectionName}] Encrypting and uploading thumbnail (${thumbJpeg.length} bytes)...`, diff --git a/test/cli/metadata-backup.test.ts b/test/cli/metadata-backup.test.ts index 23d13ff..4a0fb49 100644 --- a/test/cli/metadata-backup.test.ts +++ b/test/cli/metadata-backup.test.ts @@ -42,7 +42,7 @@ import { deriveLoginSubkey, encryptBlob, } from "../../src/crypto/index.js"; -import sharp from "sharp"; +import * as jpegJs from "jpeg-js"; import { Client } from "../../src/client.js"; import { runMetadataBackup } from "../../src/metadata-backup.js"; import type { KeyAttributes } from "../../src/auth/types.js"; @@ -290,11 +290,19 @@ const buildMetaMock = async (): Promise => { ); // Generate a real JPEG for EXIF extraction tests - const tinyJpeg = await sharp({ - create: { width: 100, height: 80, channels: 3, background: "red" }, - }) - .jpeg({ quality: 80 }) - .toBuffer(); + const jw = 100; + const jh = 80; + const jpixels = new Uint8Array(jw * jh * 4); + for (let i = 0; i < jpixels.length; i += 4) { + jpixels[i] = 255; + jpixels[i + 1] = 0; + jpixels[i + 2] = 0; + jpixels[i + 3] = 255; + } + const tinyJpeg = jpegJs.encode( + { data: jpixels, width: jw, height: jh }, + 80, + ).data; const filePush1 = sodium.crypto_secretstream_xchacha20poly1305_init_push(fk1); const encFileBody1 = sodium.crypto_secretstream_xchacha20poly1305_push( @@ -606,11 +614,10 @@ describe("quak backup-metadata", () => { ), ); - // imageMetadata from sharp should be present + // imageMetadata from JPEG parsing should be present expect(fileMeta.imageMetadata).toBeDefined(); expect(fileMeta.imageMetadata.format).toBe("jpeg"); expect(fileMeta.imageMetadata.width).toBe(100); expect(fileMeta.imageMetadata.height).toBe(80); - expect(fileMeta.imageMetadata.channels).toBe(3); }); }); diff --git a/test/thumbnails/thumbnails.test.ts b/test/thumbnails/thumbnails.test.ts index ac00a87..314eb3d 100644 --- a/test/thumbnails/thumbnails.test.ts +++ b/test/thumbnails/thumbnails.test.ts @@ -7,7 +7,7 @@ * verify that the detection and repair logic handles each case correctly. * * `fixMissingThumbnails` is the most complex function in quak: it - * downloads the original file, generates a JPEG thumbnail with sharp, + * downloads the original file, generates a JPEG thumbnail with jpeg-js, * encrypts it with secretstream push, gets a presigned upload URL, * uploads to S3, and registers the new thumbnail with the API. The * test verifies each step actually happened and the uploaded data is @@ -15,7 +15,7 @@ */ import sodium from "libsodium-wrappers-sumo"; -import sharp from "sharp"; +import * as jpegJs from "jpeg-js"; import { beforeAll, describe, expect, it } from "vitest"; import { init, @@ -120,12 +120,20 @@ const buildThumbMock = async (): Promise => { updationTime: 1700000000000000, }; - // Generate a real tiny JPEG that sharp can process - const tinyJpeg = await sharp({ - create: { width: 100, height: 80, channels: 3, background: "red" }, - }) - .jpeg({ quality: 80 }) - .toBuffer(); + // Generate a real tiny JPEG via jpeg-js + const w = 100; + const h = 80; + const pixels = new Uint8Array(w * h * 4); + for (let i = 0; i < pixels.length; i += 4) { + pixels[i] = 255; // R + pixels[i + 1] = 0; // G + pixels[i + 2] = 0; // B + pixels[i + 3] = 255; // A + } + const tinyJpeg = jpegJs.encode( + { data: pixels, width: w, height: h }, + 80, + ).data; const fileKeys: Record = {}; const fileCiphertexts: Record = {}; @@ -434,7 +442,7 @@ describe("fixMissingThumbnails", () => { expect(decrypted[0]).toBe(0xff); expect(decrypted[1]).toBe(0xd8); expect(decrypted[2]).toBe(0xff); - // Verify sharp produced a reasonably sized thumbnail + // Verify jpeg-js produced a reasonably sized thumbnail expect(decrypted.length).toBeGreaterThan(100); expect(decrypted.length).toBeLessThan(50000); }); diff --git a/yarn.lock b/yarn.lock index cb84981..f1c3713 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,13 +2,6 @@ # yarn lockfile v1 -"@emnapi/runtime@^1.7.0": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.10.0.tgz#4b260c0d3534204e98c6110b8db1a987d26ec87c" - integrity sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA== - dependencies: - tslib "^2.4.0" - "@esbuild/aix-ppc64@0.21.5": version "0.21.5" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" @@ -230,153 +223,6 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== -"@img/colour@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@img/colour/-/colour-1.1.0.tgz#b0c2c2fa661adf75effd6b4964497cd80010bb9d" - integrity sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ== - -"@img/sharp-darwin-arm64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz#6e0732dcade126b6670af7aa17060b926835ea86" - integrity sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w== - optionalDependencies: - "@img/sharp-libvips-darwin-arm64" "1.2.4" - -"@img/sharp-darwin-x64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz#19bc1dd6eba6d5a96283498b9c9f401180ee9c7b" - integrity sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw== - optionalDependencies: - "@img/sharp-libvips-darwin-x64" "1.2.4" - -"@img/sharp-libvips-darwin-arm64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz#2894c0cb87d42276c3889942e8e2db517a492c43" - integrity sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g== - -"@img/sharp-libvips-darwin-x64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz#e63681f4539a94af9cd17246ed8881734386f8cc" - integrity sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg== - -"@img/sharp-libvips-linux-arm64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz#b1b288b36864b3bce545ad91fa6dadcf1a4ad318" - integrity sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw== - -"@img/sharp-libvips-linux-arm@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz#b9260dd1ebe6f9e3bdbcbdcac9d2ac125f35852d" - integrity sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A== - -"@img/sharp-libvips-linux-ppc64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz#4b83ecf2a829057222b38848c7b022e7b4d07aa7" - integrity sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA== - -"@img/sharp-libvips-linux-riscv64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz#880b4678009e5a2080af192332b00b0aaf8a48de" - integrity sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA== - -"@img/sharp-libvips-linux-s390x@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz#74f343c8e10fad821b38f75ced30488939dc59ec" - integrity sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ== - -"@img/sharp-libvips-linux-x64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz#df4183e8bd8410f7d61b66859a35edeab0a531ce" - integrity sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw== - -"@img/sharp-libvips-linuxmusl-arm64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz#c8d6b48211df67137541007ee8d1b7b1f8ca8e06" - integrity sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw== - -"@img/sharp-libvips-linuxmusl-x64@1.2.4": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz#be11c75bee5b080cbee31a153a8779448f919f75" - integrity sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg== - -"@img/sharp-linux-arm64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz#7aa7764ef9c001f15e610546d42fce56911790cc" - integrity sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg== - optionalDependencies: - "@img/sharp-libvips-linux-arm64" "1.2.4" - -"@img/sharp-linux-arm@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz#5fb0c3695dd12522d39c3ff7a6bc816461780a0d" - integrity sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw== - optionalDependencies: - "@img/sharp-libvips-linux-arm" "1.2.4" - -"@img/sharp-linux-ppc64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz#9c213a81520a20caf66978f3d4c07456ff2e0813" - integrity sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA== - optionalDependencies: - "@img/sharp-libvips-linux-ppc64" "1.2.4" - -"@img/sharp-linux-riscv64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz#cdd28182774eadbe04f62675a16aabbccb833f60" - integrity sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw== - optionalDependencies: - "@img/sharp-libvips-linux-riscv64" "1.2.4" - -"@img/sharp-linux-s390x@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz#93eac601b9f329bb27917e0e19098c722d630df7" - integrity sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg== - optionalDependencies: - "@img/sharp-libvips-linux-s390x" "1.2.4" - -"@img/sharp-linux-x64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz#55abc7cd754ffca5002b6c2b719abdfc846819a8" - integrity sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ== - optionalDependencies: - "@img/sharp-libvips-linux-x64" "1.2.4" - -"@img/sharp-linuxmusl-arm64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz#d6515ee971bb62f73001a4829b9d865a11b77086" - integrity sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64" "1.2.4" - -"@img/sharp-linuxmusl-x64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz#d97978aec7c5212f999714f2f5b736457e12ee9f" - integrity sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q== - optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64" "1.2.4" - -"@img/sharp-wasm32@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz#2f15803aa626f8c59dd7c9d0bbc766f1ab52cfa0" - integrity sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw== - dependencies: - "@emnapi/runtime" "^1.7.0" - -"@img/sharp-win32-arm64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz#3706e9e3ac35fddfc1c87f94e849f1b75307ce0a" - integrity sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g== - -"@img/sharp-win32-ia32@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz#0b71166599b049e032f085fb9263e02f4e4788de" - integrity sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg== - -"@img/sharp-win32-x64@0.34.5": - version "0.34.5" - resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz#a81ffb00e69267cd0a1d626eaedb8a8430b2b2f8" - integrity sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw== - "@jridgewell/sourcemap-codec@^1.5.5": version "1.5.5" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" @@ -557,13 +403,6 @@ dependencies: undici-types "~6.21.0" -"@types/sharp@0.32.0": - version "0.32.0" - resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.32.0.tgz#fc3ac6df6b456319bae807c3d24efdc6631cdd6f" - integrity sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw== - dependencies: - sharp "*" - "@typescript-eslint/eslint-plugin@8.46.2": version "8.46.2" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz#dc4ab93ee3d7e6c8e38820a0d6c7c93c7183e2dc" @@ -877,11 +716,6 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -detect-libc@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" - integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== - env-paths@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-4.0.0.tgz#d0bb1f84a81d2542581bf7b7e8085d0683b39097" @@ -1026,7 +860,7 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -exif-reader@^2.0.3: +exif-reader@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/exif-reader/-/exif-reader-2.0.3.tgz#259997735080bc6bb959c37b32c60f004ec4391d" integrity sha512-zFbQvguwT9JkqyYhR7pjE1Yn8SagwaGLNRU0Oh14xFa1paSf5Gzxn4gxgk0XhnudI0UIqU+HgnBX93+nva592A== @@ -1193,6 +1027,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jpeg-js@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" + integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== + js-yaml@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" @@ -1451,45 +1290,11 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -semver@^7.6.0, semver@^7.7.3: +semver@^7.6.0: version "7.8.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.0.tgz#ed0661039fcbcda2ce71f01fa6adbefaa77040df" integrity sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA== -sharp@*, sharp@0.34.5: - version "0.34.5" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.34.5.tgz#b6f148e4b8c61f1797bde11a9d1cfebbae2c57b0" - integrity sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg== - dependencies: - "@img/colour" "^1.0.0" - detect-libc "^2.1.2" - semver "^7.7.3" - optionalDependencies: - "@img/sharp-darwin-arm64" "0.34.5" - "@img/sharp-darwin-x64" "0.34.5" - "@img/sharp-libvips-darwin-arm64" "1.2.4" - "@img/sharp-libvips-darwin-x64" "1.2.4" - "@img/sharp-libvips-linux-arm" "1.2.4" - "@img/sharp-libvips-linux-arm64" "1.2.4" - "@img/sharp-libvips-linux-ppc64" "1.2.4" - "@img/sharp-libvips-linux-riscv64" "1.2.4" - "@img/sharp-libvips-linux-s390x" "1.2.4" - "@img/sharp-libvips-linux-x64" "1.2.4" - "@img/sharp-libvips-linuxmusl-arm64" "1.2.4" - "@img/sharp-libvips-linuxmusl-x64" "1.2.4" - "@img/sharp-linux-arm" "0.34.5" - "@img/sharp-linux-arm64" "0.34.5" - "@img/sharp-linux-ppc64" "0.34.5" - "@img/sharp-linux-riscv64" "0.34.5" - "@img/sharp-linux-s390x" "0.34.5" - "@img/sharp-linux-x64" "0.34.5" - "@img/sharp-linuxmusl-arm64" "0.34.5" - "@img/sharp-linuxmusl-x64" "0.34.5" - "@img/sharp-wasm32" "0.34.5" - "@img/sharp-win32-arm64" "0.34.5" - "@img/sharp-win32-ia32" "0.34.5" - "@img/sharp-win32-x64" "0.34.5" - shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" @@ -1571,11 +1376,6 @@ ts-api-utils@^2.1.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz#4acd4a155e22734990a5ed1fe9e97f113bcb37c1" integrity sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA== -tslib@^2.4.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" - integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"