From ed732be999db379c769041c29ae80ac1307b17b7 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 11 Oct 2026 23:45:56 +0900 Subject: [PATCH] =?UTF-8?q?=EC=83=81=EC=9C=84=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=84=B0=EB=A6=AC=20=EB=B0=8F=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EC=9D=98=20=EC=86=8C=EC=9C=A0=EA=B6=8C=EC=9D=84=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8D=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EB=93=B1=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20Vitest=20=EA=B8=B0=EB=B0=98=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- package.json | 6 +- pnpm-lock.yaml | 1343 ++++++++++++++++- pnpm-workspace.yaml | 3 + src/hooks.client.ts | 3 + src/lib/modules/crypto/utils.ts | 4 +- src/lib/server/db/category.ts | 22 + src/lib/server/db/directory.ts | 22 + src/lib/server/db/upload.ts | 3 + src/lib/server/services/file.ts | 3 +- src/lib/server/services/upload.ts | 2 +- src/lib/serviceWorker/client.ts | 22 + src/lib/serviceWorker/types.ts | 8 +- .../download/+server.ts | 6 +- src/service-worker/handlers/decryptFile.ts | 8 +- src/service-worker/index.ts | 2 +- src/service-worker/modules/metadata.ts | 46 + src/service-worker/stores.ts | 24 +- src/trpc/routers/category.ts | 8 +- src/trpc/routers/directory.ts | 8 +- src/trpc/routers/upload.ts | 2 + tests/helpers/database.ts | 84 ++ tests/server/download.test.ts | 52 + tests/server/ownership.test.ts | 64 + tests/server/upload.test.ts | 35 + tests/setup/database.ts | 53 + tests/setup/polyfills.ts | 13 + tests/setup/server.ts | 20 + tests/unit/concurrency.test.ts | 92 ++ tests/unit/crypto.test.ts | 45 + tests/unit/range.test.ts | 68 + vitest.config.ts | 28 + 31 files changed, 2057 insertions(+), 42 deletions(-) create mode 100644 src/service-worker/modules/metadata.ts create mode 100644 tests/helpers/database.ts create mode 100644 tests/server/download.test.ts create mode 100644 tests/server/ownership.test.ts create mode 100644 tests/server/upload.test.ts create mode 100644 tests/setup/database.ts create mode 100644 tests/setup/polyfills.ts create mode 100644 tests/setup/server.ts create mode 100644 tests/unit/concurrency.test.ts create mode 100644 tests/unit/crypto.test.ts create mode 100644 tests/unit/range.test.ts create mode 100644 vitest.config.ts diff --git a/package.json b/package.json index 17d8643..f3e0dc8 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "format": "prettier --write .", "lint": "prettier --check . && eslint .", + "test": "vitest run", + "test:watch": "vitest", "db:up": "docker compose -f docker-compose.dev.yaml -p arkvault-dev up -d", "db:down": "docker compose -f docker-compose.dev.yaml -p arkvault-dev down", "db:migrate": "kysely migrate" @@ -27,6 +29,7 @@ "@sveltejs/vite-plugin-svelte": "^7.3.1", "@tailwindcss/vite": "^4.3.3", "@tanstack/svelte-virtual": "^3.13.40", + "@testcontainers/postgresql": "^12.2.0", "@trpc/client": "^11.19.0", "@types/file-saver": "^2.0.7", "@types/ms": "^2.1.0", @@ -56,7 +59,8 @@ "typescript": "^6.0.3", "typescript-eslint": "^8.71.1", "unplugin-icons": "^24.0.0", - "vite": "^8.3.4" + "vite": "^8.3.4", + "vitest": "^5.0.3" }, "dependencies": { "@trpc/server": "^11.19.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d283b7d..81dd0e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,22 +50,25 @@ importers: version: 2.4.0 '@sveltejs/adapter-node': specifier: ^6.0.0 - version: 6.0.0(@sveltejs/kit@3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7))) + version: 6.0.0(@sveltejs/kit@3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1))) '@sveltejs/kit': specifier: ^3.0.1 - version: 3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)) + version: 3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) '@sveltejs/load-config': specifier: ^0.2.2 version: 0.2.3 '@sveltejs/vite-plugin-svelte': specifier: ^7.3.1 - version: 7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)) + version: 7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) '@tailwindcss/vite': specifier: ^4.3.3 - version: 4.3.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)) + version: 4.3.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) '@tanstack/svelte-virtual': specifier: ^3.13.40 version: 3.13.40(svelte@5.57.2(@typescript-eslint/types@8.71.1)) + '@testcontainers/postgresql': + specifier: ^12.2.0 + version: 12.2.0 '@trpc/client': specifier: ^11.19.0 version: 11.19.0(@trpc/server@11.19.0(typescript@6.0.3))(typescript@6.0.3) @@ -152,10 +155,13 @@ importers: version: 8.71.1(eslint@10.12.0(jiti@1.21.7))(typescript@6.0.3) unplugin-icons: specifier: ^24.0.0 - version: 24.0.0(rolldown@1.2.13)(rollup@4.64.4)(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)) + version: 24.0.0(rolldown@1.2.13)(rollup@4.64.4)(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) vite: specifier: ^8.3.4 - version: 8.3.4(@types/node@26.6.5)(jiti@1.21.7) + version: 8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1) + vitest: + specifier: ^5.0.3 + version: 5.0.3(@types/node@26.6.5)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) packages: @@ -165,6 +171,9 @@ packages: '@antfu/install-pkg@2.1.0': resolution: {integrity: sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==} + '@balena/dockerignore@1.0.2': + resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} + '@cacheable/memory@2.2.0': resolution: {integrity: sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==} @@ -222,6 +231,20 @@ packages: resolution: {integrity: sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} + '@grpc/grpc-js@1.14.6': + resolution: {integrity: sha512-SgC/B3/VZ8lTyOZ4Cwk6f1skWhmkNAaVfiX90qd5u1rRXGKk1//8FTUEN9GvOUq7rfWIqAYkA3CoyumYVpOS6g==} + engines: {node: '>=12.10.0'} + + '@grpc/proto-loader@0.7.15': + resolution: {integrity: sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==} + engines: {node: '>=6'} + hasBin: true + + '@grpc/proto-loader@0.8.1': + resolution: {integrity: sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==} + engines: {node: '>=6'} + hasBin: true + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -251,6 +274,10 @@ packages: '@iconify/utils@3.1.7': resolution: {integrity: sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w==} + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -267,6 +294,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@js-sdsl/ordered-map@4.4.2': + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@keyv/bigmap@1.3.1': resolution: {integrity: sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==} engines: {node: '>= 18'} @@ -276,6 +306,9 @@ packages: '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + '@napi-rs/lzma-linux-x64-gnu@1.5.1': resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} engines: {node: ^22.20 || ^24.12 || >=25} @@ -294,6 +327,10 @@ packages: resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} engines: {node: '>=10'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@pkgr/core@0.3.6': resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -301,6 +338,33 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.2': + resolution: {integrity: sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==} + '@rolldown/binding-android-arm-eabi@1.2.13': resolution: {integrity: sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -680,6 +744,9 @@ packages: '@tanstack/virtual-core@3.18.0': resolution: {integrity: sha512-8frWsyfyBwLGoDQ6wN6Lq3CoT8j4BPJUzE6nH4P9mruLhxPOamk82prC3JxRMLJzYnNa9ISaZ8NtfxEiOGkHEQ==} + '@testcontainers/postgresql@12.2.0': + resolution: {integrity: sha512-14oNBbKdqIkWTiw8XwP6YqWnuBebiij8m4fF03WTSHvNt5FfvQpGTLwIV3p07UpDnqWRFvY50YFzbiYdB+SWuA==} + '@trpc/client@11.19.0': resolution: {integrity: sha512-0MKkPq6MejsKB5Opca1Rl22KVIaqo2+A1EiXyZ8EGUsl9TLrPdpGn+Usux20TZKFwnSC2+wI/WilqNJaP4ODfw==} hasBin: true @@ -693,6 +760,18 @@ packages: peerDependencies: typescript: '>=5.7.2' + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/docker-modem@3.0.6': + resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} + + '@types/dockerode@4.0.2': + resolution: {integrity: sha512-hA4myZ0XzwHG/p/eCLElYsQS3y1XZfvi6AfLFKVp1SNpPkO6J3xLatacAFVkReTxXRoIa3eHJmhJi9w6I3+zNA==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -711,12 +790,24 @@ packages: '@types/node-schedule@2.1.8': resolution: {integrity: sha512-k00g6Yj/oUg/CDC+MeLHUzu0+OFxWbIqrFfDiLi6OPKxTujvpv29mHGM8GtKr7B+9Vv92FcK/8mRqi1DK5f3hA==} + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + '@types/node@26.6.5': resolution: {integrity: sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==} '@types/pg@8.23.1': resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} + '@types/ssh2-streams@0.1.13': + resolution: {integrity: sha512-faHyY3brO9oLEA0QlcO8N2wT7R0+1sHWZvQ+y3rMLwdY1ZyS1z0W3t65j9PqT4HmQ6ALzNe7RZlNuCNE0wBSWA==} + + '@types/ssh2@0.5.52': + resolution: {integrity: sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg==} + + '@types/ssh2@1.15.6': + resolution: {integrity: sha512-oGdxhBqcRTwSTKFm+9EiKzkNVYRLEFkcW44lhguvBalGJbWfGnDt/ezwSUZc+SF9m9bMc3VyklNAtp7zICjS5w==} + '@typescript-eslint/eslint-plugin@8.71.1': resolution: {integrity: sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -776,10 +867,28 @@ packages: resolution: {integrity: sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/mocker@5.0.3': + resolution: {integrity: sha512-T8sWAIbkSyAjkwTcaEc3Iu0o9A27X1/kdXrizhZkGuSKScRQtRzclfAMpOTcGdXCsqxeWlpGy3XjqaW8CpLORg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + '@xmldom/xmldom@0.9.12': resolution: {integrity: sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==} engines: {node: '>=14.6'} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -797,6 +906,30 @@ packages: ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.4.0: + resolution: {integrity: sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + archiver-utils@5.0.2: + resolution: {integrity: sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==} + engines: {node: '>= 14'} + + archiver@7.0.1: + resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==} + engines: {node: '>= 14'} + argon2@0.45.1: resolution: {integrity: sha512-skm+/WCjkGqCQxF7FG1LuZXM5yvbFjgbfiCGsud2oLgaDhh6b6dbH0b1EkghbM+xx4Bj8Ape+KKgixoIlWZicQ==} engines: {node: '>=16.17.0'} @@ -805,6 +938,19 @@ packages: resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} engines: {node: '>= 0.4'} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + async-lock@1.4.1: + resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -815,14 +961,92 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + b4a@1.9.0: + resolution: {integrity: sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + bare-events@2.9.2: + resolution: {integrity: sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.8.4: + resolution: {integrity: sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==} + engines: {bare: '>=1.28.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-path@3.1.2: + resolution: {integrity: sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==} + + bare-stream@2.13.4: + resolution: {integrity: sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.5.4: + resolution: {integrity: sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + brace-expansion@2.1.7: + resolution: {integrity: sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==} + brace-expansion@5.0.12: resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} engines: {node: 20 || >=22} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + buildcheck@0.0.7: + resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} + engines: {node: '>=10.0.0'} + + byline@5.0.0: + resolution: {integrity: sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q==} + engines: {node: '>=0.10.0'} + c12@3.3.4: resolution: {integrity: sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==} peerDependencies: @@ -838,6 +1062,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + chai@6.3.0: + resolution: {integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==} + engines: {node: '>=18'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -846,17 +1074,35 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + citty@0.2.2: resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + compress-commons@6.0.2: + resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==} + engines: {node: '>= 14'} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -878,6 +1124,22 @@ packages: resolution: {integrity: sha512-vePst2VLTSqPglW7Q98VwFpGiGpOHTyXIo81MO5VyLecA5zMbZw/P/V1vmwD1jksa2iJpn/YkDrJ3XhTfzUyhQ==} engines: {node: '>=18'} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cpu-features@0.0.10: + resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} + engines: {node: '>=10.0.0'} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@6.0.0: + resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==} + engines: {node: '>= 14'} + cron-parser@4.9.0: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} @@ -933,6 +1195,18 @@ packages: dexie@4.4.6: resolution: {integrity: sha512-hJP/BO6mjB+tX6hToIO1kmxYLNmun90wYbfcoAoLpKEyDYal/k33dg0TAfoUe2TDsbbLoVIzljJ3BN2UbR5EOg==} + docker-compose@1.5.0: + resolution: {integrity: sha512-O+eD6c63+BJORUWddXA7uAlI6H1KLDPd/aS14k73nXYpFliM48C3xrrKB3sXsR1Fp/rHUZQe7+fJ+QA7+jepyw==} + engines: {node: '>= 6.0.0'} + + docker-modem@5.0.7: + resolution: {integrity: sha512-XJgGhoR/CLpqshm4d3L7rzH6t8NgDFUIIpztYlLHIApeJjMZKYJMz2zxPsYxnejq5h3ELYSw/RBsi3t5h7gNTA==} + engines: {node: '>= 8.0'} + + dockerode@5.0.1: + resolution: {integrity: sha512-avsq/xk4YPIrn0CgleX5bjT9Y8IT1p9PxrNQ++RBQ2WEyFfHCTDsT9kmyxz+H/axnjAwg8wJWEIuPGOUuNupiA==} + engines: {node: '>= 14.17'} + dotenv@17.4.2: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} @@ -941,6 +1215,18 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.26.0: resolution: {integrity: sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==} engines: {node: '>=10.13.0'} @@ -956,6 +1242,9 @@ packages: es-hangul@2.4.0: resolution: {integrity: sha512-9ouVct+rsUw7d5+JeyEV+Lf4PAytSK4cWnLGHM4FJDyG9BS5d3iSPnEmH/rVgmSyxyps5cWZ+NeDAlJyq8eKaw==} + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + es-object-atoms@1.1.2: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} @@ -964,6 +1253,10 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1056,20 +1349,41 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + exifreader@4.48.0: resolution: {integrity: sha512-zDKM9DuTgK9eWYzpROPAVkVf3cMKrQLdBFPfMq489pzQpHWzDhc6ZetDKANZ15GDTjaE/pEuk6iLXJpe+z41Qw==} hasBin: true + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + exsolve@1.1.3: resolution: {integrity: sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -1110,10 +1424,17 @@ packages: debug: optional: true + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.6: resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1122,10 +1443,18 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1138,6 +1467,11 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + globals@16.5.0: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} @@ -1182,6 +1516,9 @@ packages: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1197,10 +1534,17 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1208,9 +1552,19 @@ packages: is-reference@3.0.3: resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -1252,6 +1606,10 @@ packages: resolution: {integrity: sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg==} engines: {node: '>=22.0.0'} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1419,9 +1777,21 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + long-timeout@0.1.1: resolution: {integrity: sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==} + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.3: resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} engines: {node: 20 || >=22} @@ -1457,6 +1827,26 @@ packages: resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -1471,6 +1861,9 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nan@2.29.0: + resolution: {integrity: sha512-GlGk3HIvitbvs+LT3g6XUP1kpirKNvmDFwF/bmo6XNWSb/eYEs/O4bfgIEIXCZ+lIOTS5xNwDvSGMw6FJdAhtA==} + nanoid@3.3.20: resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1494,6 +1887,10 @@ packages: resolution: {integrity: sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==} engines: {node: '>=6'} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + nypm@0.6.10: resolution: {integrity: sha512-W72Hrj1petq+b3Hk2aAC+9zswetlIFTnDW4s5djseZh2nYqBbyQLOtj472HwcbcWykPBUW1WpWpjOd4nK6gMRw==} engines: {node: '>=18'} @@ -1509,6 +1906,9 @@ packages: ohash@2.0.12: resolution: {integrity: sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1525,6 +1925,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@1.9.0: resolution: {integrity: sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==} @@ -1536,6 +1939,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1714,10 +2121,31 @@ packages: engines: {node: '>=14'} hasBin: true + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + + properties-reader@3.0.1: + resolution: {integrity: sha512-WPn+h9RGEExOKdu4bsF4HksG/uzd3cFq3MFtq8PsFeExPse5Ha/VOjQNyHhjboBFwGXGev6muJYTSPAOkROq2g==} + engines: {node: '>=18'} + + protobufjs@7.6.6: + resolution: {integrity: sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==} + engines: {node: '>=12.0.0'} + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1732,6 +2160,20 @@ packages: rc9@3.1.0: resolution: {integrity: sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.8.0: + resolution: {integrity: sha512-2jhTTU3Eilp/2DMT71gbcoqC35eQHasMJH0RHkYxwlxIv3+FR6Y5xAFZIwEGLxKkjZYEFlsiMNIdwUqjK2lLHQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -1740,6 +2182,14 @@ packages: resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==} engines: {node: '>= 20.19.0'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + rolldown@1.2.13: resolution: {integrity: sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1754,6 +2204,15 @@ packages: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.8.5: resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} @@ -1767,6 +2226,13 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -1778,13 +2244,48 @@ packages: resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} + split-ca@1.0.1: + resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} + ssh-remote-port-forward@1.0.4: + resolution: {integrity: sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ==} + + ssh2@1.17.0: + resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} + engines: {node: '>=10.16.0'} + std-env@4.3.0: resolution: {integrity: sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==} + streamx@2.28.1: + resolution: {integrity: sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + superjson@2.2.6: resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} engines: {node: '>=16'} @@ -1826,6 +2327,33 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tar-fs@2.1.5: + resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==} + + tar-fs@3.1.3: + resolution: {integrity: sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar-stream@3.2.2: + resolution: {integrity: sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + testcontainers@12.2.0: + resolution: {integrity: sha512-Pd0c2eU0DdIuK+Pb3c+vmBUdKjDLgxZmPecQGESFAKyn5LTqSaSw8l7htfMlLwabb/QsUILe6XkxMJw8Ly/ThA==} + engines: {node: '>= 22.22'} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + tinybench@6.2.1: + resolution: {integrity: sha512-IAbQaPJIIdhVxi0mqy9lez1wEwje0UQf9F1vwtbelEG5Nbgn0nPOxCvU+mNeNS9gyJTAhrHp74CbUaSynQpZcQ==} + engines: {node: '>=20.0.0'} + tinyexec@1.3.1: resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} engines: {node: '>=18'} @@ -1834,6 +2362,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} @@ -1844,6 +2376,9 @@ packages: peerDependencies: typescript: '>=4.8.4' + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -1863,9 +2398,16 @@ packages: ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} + undici@8.11.2: + resolution: {integrity: sha512-u4UB2/IrKdU6lFxumHmmo1a3fCQO5tzQllRorfoRS63txhrB7xTpSn1PftwC4qEHkOaqP95fCWW4lJzwErwzhQ==} + engines: {node: '>=22.19.0'} + unplugin-icons@24.0.0: resolution: {integrity: sha512-wV6c7I0PtiIP8KIU7Z1Ukyc2uzgY9ind3RXNu7j63L9qTWDn7CzLnW9FD57DKbfGpWKQv3qyV2PCtBG8Y5Llcg==} peerDependencies: @@ -1983,6 +2525,47 @@ packages: vite: optional: true + vitest@5.0.3: + resolution: {integrity: sha512-xMw97S3rjdtj5dkVat7jCsqWBpvchs3RlpQctUqwJD0KkERk40vz2fJ77lDwW/Vzh/pk18eItYAzkodhSes3jQ==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 5.0.3 + '@vitest/browser-preview': 5.0.3 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.3 + '@vitest/coverage-v8': 5.0.3 + '@vitest/ui': 5.0.3 + happy-dom: '*' + jsdom: '*' + vite: ^6.4.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} @@ -1991,18 +2574,51 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@3.2.1: + resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} + engines: {node: '>=20.11'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yaml@1.10.3: resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} engines: {node: '>= 6'} + yaml@2.9.1: + resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2014,6 +2630,10 @@ packages: zimmerframe@1.1.5: resolution: {integrity: sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==} + zip-stream@6.0.1: + resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} + engines: {node: '>= 14'} + zod@4.6.5: resolution: {integrity: sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==} @@ -2029,6 +2649,8 @@ snapshots: package-manager-detector: 1.9.0 tinyexec: 1.3.1 + '@balena/dockerignore@1.0.2': {} + '@cacheable/memory@2.2.0': dependencies: '@cacheable/utils': 2.5.0 @@ -2083,6 +2705,25 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 + '@grpc/grpc-js@1.14.6': + dependencies: + '@grpc/proto-loader': 0.8.1 + '@js-sdsl/ordered-map': 4.4.2 + + '@grpc/proto-loader@0.7.15': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + + '@grpc/proto-loader@0.8.1': + dependencies: + lodash.camelcase: 4.3.0 + long: 5.3.2 + protobufjs: 7.6.6 + yargs: 17.7.3 + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -2111,6 +2752,15 @@ snapshots: '@iconify/types': 2.0.0 import-meta-resolve: 4.2.0 + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.6.0 @@ -2130,6 +2780,8 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.6.0 + '@js-sdsl/ordered-map@4.4.2': {} + '@keyv/bigmap@1.3.1(keyv@5.6.0)': dependencies: hashery: 1.5.1 @@ -2138,6 +2790,12 @@ snapshots: '@keyv/serialize@1.1.1': {} + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@napi-rs/lzma-linux-x64-gnu@1.5.1': optional: true @@ -2147,10 +2805,33 @@ snapshots: '@phc/format@1.0.0': {} + '@pkgjs/parseargs@0.11.0': + optional: true + '@pkgr/core@0.3.6': {} '@polka/url@1.0.0-next.29': {} + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.2': {} + '@rolldown/binding-android-arm-eabi@1.2.13': optional: true @@ -2279,15 +2960,15 @@ snapshots: dependencies: acorn: 8.19.0 - '@sveltejs/adapter-node@6.0.0(@sveltejs/kit@3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)))': + '@sveltejs/adapter-node@6.0.0(@sveltejs/kit@3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)))': dependencies: - '@sveltejs/kit': 3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)) + '@sveltejs/kit': 3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) - '@sveltejs/kit@3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7))': + '@sveltejs/kit@3.0.1(@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)))(svelte@5.57.2(@typescript-eslint/types@8.71.1))(typescript@6.0.3)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1))': dependencies: '@rolldown/pluginutils': 1.0.1 '@standard-schema/spec': 1.1.0 - '@sveltejs/vite-plugin-svelte': 7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)) + '@sveltejs/vite-plugin-svelte': 7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) cookie: 2.0.1 devalue: 5.9.4 esm-env: 1.2.2 @@ -2295,20 +2976,20 @@ snapshots: mrmime: 2.0.1 sirv: 3.0.2 svelte: 5.57.2(@typescript-eslint/types@8.71.1) - vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7) + vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1) optionalDependencies: typescript: 6.0.3 '@sveltejs/load-config@0.2.3': {} - '@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7))': + '@sveltejs/vite-plugin-svelte@7.3.1(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1))': dependencies: deepmerge: 4.3.1 magic-string: 1.4.3 obug: 2.2.1 svelte: 5.57.2(@typescript-eslint/types@8.71.1) - vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7) - vitefu: 1.1.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)) + vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1) + vitefu: 1.1.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) '@tailwindcss/node@4.3.3': dependencies: @@ -2371,12 +3052,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7))': + '@tailwindcss/vite@4.3.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7) + vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1) '@tanstack/svelte-virtual@3.13.40(svelte@5.57.2(@typescript-eslint/types@8.71.1))': dependencies: @@ -2385,6 +3066,15 @@ snapshots: '@tanstack/virtual-core@3.18.0': {} + '@testcontainers/postgresql@12.2.0': + dependencies: + testcontainers: 12.2.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + '@trpc/client@11.19.0(@trpc/server@11.19.0(typescript@6.0.3))(typescript@6.0.3)': dependencies: '@trpc/server': 11.19.0(typescript@6.0.3) @@ -2394,6 +3084,24 @@ snapshots: dependencies: typescript: 6.0.3 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/docker-modem@3.0.6': + dependencies: + '@types/node': 26.6.5 + '@types/ssh2': 1.15.6 + + '@types/dockerode@4.0.2': + dependencies: + '@types/docker-modem': 3.0.6 + '@types/node': 26.6.5 + '@types/ssh2': 1.15.6 + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.9': {} @@ -2408,6 +3116,10 @@ snapshots: dependencies: '@types/node': 26.6.5 + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + '@types/node@26.6.5': dependencies: undici-types: 8.9.0 @@ -2418,6 +3130,19 @@ snapshots: pg-protocol: 1.16.1 pg-types: 2.2.0 + '@types/ssh2-streams@0.1.13': + dependencies: + '@types/node': 26.6.5 + + '@types/ssh2@0.5.52': + dependencies: + '@types/node': 26.6.5 + '@types/ssh2-streams': 0.1.13 + + '@types/ssh2@1.15.6': + dependencies: + '@types/node': 18.19.130 + '@typescript-eslint/eslint-plugin@8.71.1(@typescript-eslint/parser@8.71.1(eslint@10.12.0(jiti@1.21.7))(typescript@6.0.3))(eslint@10.12.0(jiti@1.21.7))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -2509,9 +3234,24 @@ snapshots: '@typescript-eslint/types': 8.71.1 eslint-visitor-keys: 5.0.1 + '@vitest/mocker@5.0.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.3 + estree-walker: 3.0.3 + magic-string: 1.4.3 + optionalDependencies: + vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1) + + '@vitest/spy@5.0.3': {} + '@xmldom/xmldom@0.9.12': optional: true + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.19.0): dependencies: acorn: 8.19.0 @@ -2531,6 +3271,40 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + + ansi-regex@6.4.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + archiver-utils@5.0.2: + dependencies: + glob: 10.5.0 + graceful-fs: 4.2.11 + is-stream: 2.0.1 + lazystream: 1.0.1 + lodash: 4.18.1 + normalize-path: 3.0.0 + readable-stream: 4.8.0 + + archiver@7.0.1: + dependencies: + archiver-utils: 5.0.2 + async: 3.2.6 + buffer-crc32: 1.0.0 + readable-stream: 4.8.0 + readdir-glob: 1.1.3 + tar-stream: 3.2.2 + zip-stream: 6.0.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + argon2@0.45.1: dependencies: '@phc/format': 1.0.0 @@ -2540,6 +3314,16 @@ snapshots: aria-query@5.3.1: {} + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assertion-error@2.0.1: {} + + async-lock@1.4.1: {} + + async@3.2.6: {} + asynckit@0.4.0: {} axios@1.20.0(debug@4.4.3): @@ -2554,12 +3338,78 @@ snapshots: axobject-query@4.1.0: {} + b4a@1.9.0: {} + + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + bare-events@2.9.2: {} + + bare-fs@4.8.4: + dependencies: + bare-events: 2.9.2 + bare-path: 3.1.2 + bare-stream: 2.13.4(bare-events@2.9.2) + bare-url: 2.5.4 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-path@3.1.2: {} + + bare-stream@2.13.4(bare-events@2.9.2): + dependencies: + b4a: 1.9.0 + streamx: 2.28.1 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.5.4: + dependencies: + bare-path: 3.1.2 + + base64-js@1.5.1: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + brace-expansion@2.1.7: + dependencies: + balanced-match: 1.0.2 + brace-expansion@5.0.12: dependencies: balanced-match: 4.0.4 + buffer-crc32@1.0.0: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buildcheck@0.0.7: + optional: true + + byline@5.0.0: {} + c12@3.3.4: dependencies: chokidar: 5.0.0 @@ -2588,6 +3438,8 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + chai@6.3.0: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -2596,14 +3448,36 @@ snapshots: dependencies: readdirp: 5.1.1 + chownr@1.1.4: {} + citty@0.2.2: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + clsx@2.1.1: {} + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 + compress-commons@6.0.2: + dependencies: + crc-32: 1.2.2 + crc32-stream: 6.0.0 + is-stream: 2.0.1 + normalize-path: 3.0.0 + readable-stream: 4.8.0 + confbox@0.1.8: {} confbox@0.2.4: {} @@ -2616,6 +3490,21 @@ snapshots: copy-anything@4.1.5: {} + core-util-is@1.0.3: {} + + cpu-features@0.0.10: + dependencies: + buildcheck: 0.0.7 + nan: 2.29.0 + optional: true + + crc-32@1.2.2: {} + + crc32-stream@6.0.0: + dependencies: + crc-32: 1.2.2 + readable-stream: 4.8.0 + cron-parser@4.9.0: dependencies: luxon: 3.7.2 @@ -2653,6 +3542,30 @@ snapshots: dexie@4.4.6: {} + docker-compose@1.5.0: + dependencies: + yaml: 2.9.1 + + docker-modem@5.0.7: + dependencies: + debug: 4.4.3 + readable-stream: 3.6.2 + split-ca: 1.0.1 + ssh2: 1.17.0 + transitivePeerDependencies: + - supports-color + + dockerode@5.0.1: + dependencies: + '@balena/dockerignore': 1.0.2 + '@grpc/grpc-js': 1.14.6 + '@grpc/proto-loader': 0.7.15 + docker-modem: 5.0.7 + protobufjs: 7.6.6 + tar-fs: 2.1.5 + transitivePeerDependencies: + - supports-color + dotenv@17.4.2: {} dunder-proto@1.0.1: @@ -2661,6 +3574,16 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.26.0: dependencies: graceful-fs: 4.2.11 @@ -2672,6 +3595,8 @@ snapshots: es-hangul@2.4.0: {} + es-module-lexer@2.3.2: {} + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -2683,6 +3608,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.4 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@10.1.8(eslint@10.12.0(jiti@1.21.7)): @@ -2806,16 +3733,34 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@2.0.3: {} + event-target-shim@5.0.1: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - bare-abort-controller + + events@3.3.0: {} + exifreader@4.48.0: optionalDependencies: '@xmldom/xmldom': 0.9.12 + expect-type@1.4.0: {} + exsolve@1.1.3: {} fast-deep-equal@3.1.3: {} + fast-fifo@1.3.2: {} + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -2847,6 +3792,11 @@ snapshots: optionalDependencies: debug: 4.4.3 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.6: dependencies: asynckit: 0.4.0 @@ -2855,11 +3805,15 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 + fs-constants@1.0.0: {} + fsevents@2.3.3: optional: true function-bind@1.1.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -2873,6 +3827,8 @@ snapshots: hasown: 2.0.4 math-intrinsics: 1.1.0 + get-port@5.1.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -2884,6 +3840,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + globals@16.5.0: {} globals@17.13.0: {} @@ -2919,6 +3884,8 @@ snapshots: transitivePeerDependencies: - supports-color + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.12: {} @@ -2927,8 +3894,12 @@ snapshots: imurmurhash@0.1.4: {} + inherits@2.0.4: {} + is-extglob@2.1.1: {} + is-fullwidth-code-point@3.0.0: {} + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -2937,8 +3908,18 @@ snapshots: dependencies: '@types/estree': 1.0.9 + is-stream@2.0.1: {} + + isarray@1.0.0: {} + isexe@2.0.0: {} + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jiti@1.21.7: optional: true @@ -2972,6 +3953,10 @@ snapshots: kysely@0.29.6: {} + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -3089,8 +4074,16 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.camelcase@4.3.0: {} + + lodash@4.18.1: {} + long-timeout@0.1.1: {} + long@5.3.2: {} + + lru-cache@10.4.3: {} + lru-cache@11.5.3: {} luxon@3.7.2: {} @@ -3117,6 +4110,20 @@ snapshots: dependencies: brace-expansion: 5.0.12 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.7 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.1.7 + + minipass@7.1.3: {} + + mkdirp-classic@0.5.3: {} + + mkdirp@3.0.1: {} + mlly@1.8.2: dependencies: acorn: 8.19.0 @@ -3130,6 +4137,9 @@ snapshots: ms@2.1.3: {} + nan@2.29.0: + optional: true + nanoid@3.3.20: {} natural-compare@1.4.0: {} @@ -3146,6 +4156,8 @@ snapshots: long-timeout: 0.1.1 sorted-array-functions: 1.3.0 + normalize-path@3.0.0: {} + nypm@0.6.10: dependencies: citty: 0.2.2 @@ -3162,6 +4174,10 @@ snapshots: ohash@2.0.12: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3183,12 +4199,19 @@ snapshots: dependencies: p-limit: 3.1.0 + package-json-from-dist@1.0.1: {} + package-manager-detector@1.9.0: {} path-exists@4.0.0: {} path-key@3.1.1: {} + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + pathe@2.0.3: {} perfect-debounce@2.1.0: {} @@ -3300,8 +4323,44 @@ snapshots: prettier@3.9.10: {} + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + + properties-reader@3.0.1: + dependencies: + '@kwsites/file-exists': 1.1.1 + mkdirp: 3.0.1 + transitivePeerDependencies: + - supports-color + + protobufjs@7.6.6: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.2 + '@types/node': 26.6.5 + long: 5.3.2 + proxy-from-env@2.1.0: {} + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@2.3.1: {} qified@0.10.1: @@ -3315,10 +4374,42 @@ snapshots: defu: 6.1.7 destr: 2.0.5 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.8.0: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + readdirp@4.1.2: {} readdirp@5.1.1: {} + require-directory@2.1.1: {} + + retry@0.12.0: {} + rolldown@1.2.13: dependencies: '@oxc-project/types': 0.153.0 @@ -3377,6 +4468,12 @@ snapshots: dependencies: mri: 1.2.0 + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + semver@7.8.5: {} shebang-command@2.0.0: @@ -3385,6 +4482,10 @@ snapshots: shebang-regex@3.0.0: {} + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -3395,10 +4496,62 @@ snapshots: source-map-js@1.2.2: {} + split-ca@1.0.1: {} + split2@4.2.0: {} + ssh-remote-port-forward@1.0.4: + dependencies: + '@types/ssh2': 0.5.52 + ssh2: 1.17.0 + + ssh2@1.17.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.29.0 + std-env@4.3.0: {} + streamx@2.28.1: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.4.0 + superjson@2.2.6: dependencies: copy-anything: 4.1.5 @@ -3463,6 +4616,82 @@ snapshots: tapable@2.3.3: {} + tar-fs@2.1.5: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.4 + tar-stream: 2.2.0 + + tar-fs@3.1.3: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.2 + optionalDependencies: + bare-fs: 4.8.4 + bare-path: 3.1.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar-stream@3.2.2: + dependencies: + b4a: 1.9.0 + bare-fs: 4.8.4 + fast-fifo: 1.3.2 + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + testcontainers@12.2.0: + dependencies: + '@balena/dockerignore': 1.0.2 + '@types/dockerode': 4.0.2 + archiver: 7.0.1 + async-lock: 1.4.1 + byline: 5.0.0 + debug: 4.4.3 + docker-compose: 1.5.0 + dockerode: 5.0.1 + get-port: 5.1.1 + proper-lockfile: 4.1.2 + properties-reader: 3.0.1 + ssh-remote-port-forward: 1.0.4 + tar-fs: 3.1.3 + tmp: 0.2.7 + undici: 8.11.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + text-decoder@1.2.7: + dependencies: + b4a: 1.9.0 + transitivePeerDependencies: + - react-native-b4a + + tinybench@6.2.1: {} + tinyexec@1.3.1: {} tinyglobby@0.2.17: @@ -3470,12 +4699,16 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tmp@0.2.7: {} + totalist@3.0.1: {} ts-api-utils@2.5.0(typescript@6.0.3): dependencies: typescript: 6.0.3 + tweetnacl@0.14.5: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -3495,15 +4728,19 @@ snapshots: ufo@1.6.4: {} + undici-types@5.26.5: {} + undici-types@8.9.0: {} - unplugin-icons@24.0.0(rolldown@1.2.13)(rollup@4.64.4)(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)): + undici@8.11.2: {} + + unplugin-icons@24.0.0(rolldown@1.2.13)(rollup@4.64.4)(svelte@5.57.2(@typescript-eslint/types@8.71.1))(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)): dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/utils': 3.1.7 local-pkg: 1.2.1 obug: 2.2.1 - unplugin: 3.4.0(rolldown@1.2.13)(rollup@4.64.4)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)) + unplugin: 3.4.0(rolldown@1.2.13)(rollup@4.64.4)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) optionalDependencies: svelte: 5.57.2(@typescript-eslint/types@8.71.1) transitivePeerDependencies: @@ -3518,7 +4755,7 @@ snapshots: - vite - webpack - unplugin@3.4.0(rolldown@1.2.13)(rollup@4.64.4)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)): + unplugin@3.4.0(rolldown@1.2.13)(rollup@4.64.4)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)): dependencies: '@jridgewell/remapping': 2.3.5 picomatch: 4.0.7 @@ -3526,7 +4763,7 @@ snapshots: optionalDependencies: rolldown: 1.2.13 rollup: 4.64.4 - vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7) + vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1) uri-js@4.4.1: dependencies: @@ -3536,7 +4773,7 @@ snapshots: uuid@14.0.3: {} - vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7): + vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -3547,10 +4784,32 @@ snapshots: '@types/node': 26.6.5 fsevents: 2.3.3 jiti: 1.21.7 + yaml: 2.9.1 - vitefu@1.1.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)): + vitefu@1.1.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)): optionalDependencies: - vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7) + vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1) + + vitest@5.0.3(@types/node@26.6.5)(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)): + dependencies: + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.3(vite@8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1)) + chai: 6.3.0 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 1.4.3 + obug: 2.2.1 + picomatch: 4.0.7 + std-env: 4.3.0 + tinybench: 6.2.1 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + vite: 8.3.4(@types/node@26.6.5)(jiti@1.21.7)(yaml@2.9.1) + why-is-node-running: 3.2.1 + optionalDependencies: + '@types/node': 26.6.5 + transitivePeerDependencies: + - msw webpack-virtual-modules@0.6.2: {} @@ -3558,16 +4817,54 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@3.2.1: {} + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + xtend@4.0.2: {} + y18n@5.0.8: {} + yaml@1.10.3: {} + yaml@2.9.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.3: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} zimmerframe@1.1.5: {} + zip-stream@6.0.1: + dependencies: + archiver-utils: 5.0.2 + compress-commons: 6.0.2 + readable-stream: 4.8.0 + zod@4.6.5: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4f67d1f..f9c9c54 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,7 @@ allowBuilds: argon2: true + cpu-features: false esbuild: true exifreader: true + protobufjs: false + ssh2: false diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 7bd4869..dcd160a 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -6,6 +6,7 @@ import { getHmacSecrets, } from "#lib/indexedDB/index.js"; import { prepareFileCache } from "#lib/modules/file/index.js"; +import { listenForMetadataRequests } from "#lib/serviceWorker/index.js"; import { clientKeyStore, masterKeyStore, hmacSecretStore } from "#lib/stores/index.js"; const requestPersistentStorage = async () => { @@ -44,6 +45,8 @@ const prepareHmacSecretStore = async () => { }; export const init: ClientInit = async () => { + listenForMetadataRequests(); + await Promise.all([ requestPersistentStorage(), prepareFileCache(), diff --git a/src/lib/modules/crypto/utils.ts b/src/lib/modules/crypto/utils.ts index 215eaf2..0f9f6fb 100644 --- a/src/lib/modules/crypto/utils.ts +++ b/src/lib/modules/crypto/utils.ts @@ -10,11 +10,11 @@ export const decodeString = (data: ArrayBuffer) => { }; export const encodeToBase64 = (data: ArrayBuffer | Uint8Array) => { - return btoa(String.fromCharCode(...(data instanceof ArrayBuffer ? new Uint8Array(data) : data))); + return (data instanceof ArrayBuffer ? new Uint8Array(data) : data).toBase64(); }; export const decodeFromBase64 = (data: string) => { - return Uint8Array.from(atob(data), (c) => c.charCodeAt(0)).buffer; + return Uint8Array.fromBase64(data).buffer; }; export const concatenateBuffers = (...buffers: ArrayBuffer[]) => { diff --git a/src/lib/server/db/category.ts b/src/lib/server/db/category.ts index e20138c..13dd418 100644 --- a/src/lib/server/db/category.ts +++ b/src/lib/server/db/category.ts @@ -14,6 +14,26 @@ interface Category { export type NewCategory = Omit; +export const ensureCategoryExists = async ( + trx: typeof db, + userId: number, + categoryId: CategoryId, +) => { + if (categoryId === "root") return; + + const category = await trx + .selectFrom("category") + .select("id") + .where("id", "=", categoryId) + .where("user_id", "=", userId) + .limit(1) + .forShare() + .executeTakeFirst(); + if (!category) { + throw new IntegrityError("Category not found"); + } +}; + export const registerCategory = async (params: NewCategory) => { await db.transaction().execute(async (trx) => { const mek = await trx @@ -28,6 +48,8 @@ export const registerCategory = async (params: NewCategory) => { throw new IntegrityError("Inactive MEK version"); } + await ensureCategoryExists(trx, params.userId, params.parentId); + const { categoryId } = await trx .insertInto("category") .values({ diff --git a/src/lib/server/db/directory.ts b/src/lib/server/db/directory.ts index 6c566e7..c168edd 100644 --- a/src/lib/server/db/directory.ts +++ b/src/lib/server/db/directory.ts @@ -25,6 +25,26 @@ const toDirectory = (row: Selectable): Directory => ({ isFavorite: row.is_favorite, }); +export const ensureDirectoryExists = async ( + trx: typeof db, + userId: number, + directoryId: DirectoryId, +) => { + if (directoryId === "root") return; + + const directory = await trx + .selectFrom("directory") + .select("id") + .where("id", "=", directoryId) + .where("user_id", "=", userId) + .limit(1) + .forShare() + .executeTakeFirst(); + if (!directory) { + throw new IntegrityError("Directory not found"); + } +}; + export const registerDirectory = async (params: Omit) => { await db.transaction().execute(async (trx) => { const mek = await trx @@ -39,6 +59,8 @@ export const registerDirectory = async (params: Omit(); -const isChunkUploaded = (bitmap: Buffer, chunkIndex: number) => { +export const isChunkUploaded = (bitmap: Buffer, chunkIndex: number) => { chunkIndex -= 1; const byte = bitmap[Math.floor(chunkIndex / 8)]; return !!byte && (byte & (1 << (chunkIndex % 8))) !== 0; // Postgres sucks diff --git a/src/lib/serviceWorker/client.ts b/src/lib/serviceWorker/client.ts index 9893a4a..9627d4e 100644 --- a/src/lib/serviceWorker/client.ts +++ b/src/lib/serviceWorker/client.ts @@ -3,6 +3,25 @@ import type { FileMetadata, ServiceWorkerMessage, ServiceWorkerResponse } from " const PREPARE_TIMEOUT_MS = 5000; +// The service worker may be terminated at any time, losing its in-memory metadata. +// Keep a copy here so that it can be re-requested on demand. +const preparedFiles = new Map(); +let isListeningForMetadataRequests = false; + +export const listenForMetadataRequests = () => { + if (isListeningForMetadataRequests) return; + isListeningForMetadataRequests = true; + + navigator.serviceWorker.addEventListener( + "message", + (event: MessageEvent) => { + if (event.data.type === "decryption-metadata-request") { + event.ports[0]?.postMessage(preparedFiles.get(event.data.fileId) ?? null); + } + }, + ); +}; + const getServiceWorker = async () => { const registration = await navigator.serviceWorker.ready; const sw = registration.active; @@ -13,6 +32,9 @@ const getServiceWorker = async () => { }; export const prepareFileDecryption = async (id: number, metadata: FileMetadata) => { + preparedFiles.set(id, metadata); + listenForMetadataRequests(); + const sw = await getServiceWorker(); return new Promise((resolve, reject) => { const timeout = setTimeout( diff --git a/src/lib/serviceWorker/types.ts b/src/lib/serviceWorker/types.ts index 97edd6d..81bd4a0 100644 --- a/src/lib/serviceWorker/types.ts +++ b/src/lib/serviceWorker/types.ts @@ -15,5 +15,11 @@ export interface DecryptionReadyMessage { fileId: number; } +// Sent with a MessagePort to which the client replies with `FileMetadata | null` +export interface DecryptionMetadataRequestMessage { + type: "decryption-metadata-request"; + fileId: number; +} + export type ServiceWorkerMessage = DecryptionPrepareMessage; -export type ServiceWorkerResponse = DecryptionReadyMessage; +export type ServiceWorkerResponse = DecryptionReadyMessage | DecryptionMetadataRequestMessage; diff --git a/src/routes/api/file/[id]/[[thumbnail=thumbnail]]/download/+server.ts b/src/routes/api/file/[id]/[[thumbnail=thumbnail]]/download/+server.ts index 161a456..98cfb7b 100644 --- a/src/routes/api/file/[id]/[[thumbnail=thumbnail]]/download/+server.ts +++ b/src/routes/api/file/[id]/[[thumbnail=thumbnail]]/download/+server.ts @@ -17,19 +17,19 @@ const downloadHandler = async (locals: App.Locals, params: RouteParams, request: const { id } = zodRes.data; const getStream = params.thumbnail ? getFileThumbnailStream : getFileStream; - const { encContentStream, range } = await getStream( + const { encContentStream, range, isRangeRequest } = await getStream( userId, id, parseRangeHeader(request.headers.get("Range")), ); return { stream: encContentStream, - status: range ? 206 : 200, + status: isRangeRequest ? 206 : 200, headers: { "Accept-Ranges": "bytes", "Content-Length": String(range.end - range.start + 1), "Content-Type": "application/octet-stream", - ...getContentRangeHeader(range), + ...(isRangeRequest ? getContentRangeHeader(range) : {}), }, }; }; diff --git a/src/service-worker/handlers/decryptFile.ts b/src/service-worker/handlers/decryptFile.ts index 075a08c..d670c3e 100644 --- a/src/service-worker/handlers/decryptFile.ts +++ b/src/service-worker/handlers/decryptFile.ts @@ -1,8 +1,8 @@ import { DECRYPTED_FILE_URL_PREFIX, CHUNK_SIZE, ENCRYPTED_CHUNK_SIZE } from "../constants"; import { decryptChunk, getEncryptedRange, getDecryptedSize } from "../modules/crypto"; import { parseRangeHeader, getContentRangeHeader } from "../modules/http"; +import { getFileMetadata } from "../modules/metadata"; import { getFile } from "../modules/opfs"; -import { fileMetadataStore } from "../stores"; import type { FileMetadata } from "../types"; const createResponse = ( @@ -131,15 +131,15 @@ const streamFromServer = async ( ); }; -const decryptFileHandler = async (request: Request) => { +const decryptFileHandler = async (request: Request, clientId: string) => { const url = new URL(request.url); const fileId = parseInt(url.pathname.slice(DECRYPTED_FILE_URL_PREFIX.length), 10); if (isNaN(fileId)) { - throw new Response("Invalid file id", { status: 400 }); + return new Response("Invalid file id", { status: 400 }); } const downloadFilename = url.searchParams.get("download") ?? undefined; - const metadata = fileMetadataStore.get(fileId); + const metadata = await getFileMetadata(fileId, clientId); const range = parseRangeHeader(request.headers.get("Range")); const cache = await getFile(`/cache/${fileId}`); if (cache) { diff --git a/src/service-worker/index.ts b/src/service-worker/index.ts index 2441a50..b2e6ef6 100644 --- a/src/service-worker/index.ts +++ b/src/service-worker/index.ts @@ -24,7 +24,7 @@ self.addEventListener("message", (event) => { self.addEventListener("fetch", (event) => { const url = new URL(event.request.url); if (url.pathname.startsWith(DECRYPTED_FILE_URL_PREFIX)) { - event.respondWith(decryptFile(event.request)); + event.respondWith(decryptFile(event.request, event.clientId)); } }); diff --git a/src/service-worker/modules/metadata.ts b/src/service-worker/modules/metadata.ts new file mode 100644 index 0000000..77ddf42 --- /dev/null +++ b/src/service-worker/modules/metadata.ts @@ -0,0 +1,46 @@ +import { self } from "$app/service-worker"; +import { fileMetadataStore } from "../stores"; +import type { FileMetadata, ServiceWorkerResponse } from "../types"; + +const METADATA_REQUEST_TIMEOUT_MS = 3000; + +const requestFromClient = (client: Client, fileId: number) => { + return new Promise((resolve) => { + const channel = new MessageChannel(); + const timeout = setTimeout(() => { + channel.port1.close(); + resolve(null); + }, METADATA_REQUEST_TIMEOUT_MS); + + channel.port1.onmessage = (event: MessageEvent) => { + clearTimeout(timeout); + channel.port1.close(); + resolve(event.data); + }; + client.postMessage( + { type: "decryption-metadata-request", fileId } satisfies ServiceWorkerResponse, + [channel.port2], + ); + }); +}; + +export const getFileMetadata = async (fileId: number, clientId: string) => { + const cached = fileMetadataStore.get(fileId); + if (cached) return cached; + + // The worker might have been restarted since the client prepared the decryption + const requestingClient = clientId ? await self.clients.get(clientId) : undefined; + const windowClients = await self.clients.matchAll({ type: "window" }); + const candidates = [ + ...(requestingClient ? [requestingClient] : []), + ...windowClients.filter(({ id }) => id !== requestingClient?.id), + ]; + + for (const client of candidates) { + const metadata = await requestFromClient(client, fileId); + if (metadata) { + fileMetadataStore.set(fileId, metadata); + return metadata; + } + } +}; diff --git a/src/service-worker/stores.ts b/src/service-worker/stores.ts index 22d899e..0f75d4a 100644 --- a/src/service-worker/stores.ts +++ b/src/service-worker/stores.ts @@ -1,3 +1,25 @@ import type { FileMetadata } from "./types"; -export const fileMetadataStore = new Map(); +const MAX_FILE_METADATA_ENTRIES = 32; + +const fileMetadata = new Map(); + +export const fileMetadataStore = { + get(fileId: number) { + const metadata = fileMetadata.get(fileId); + if (metadata) { + // Move to the end to keep the most recently used entries + fileMetadata.delete(fileId); + fileMetadata.set(fileId, metadata); + } + return metadata; + }, + set(fileId: number, metadata: FileMetadata) { + fileMetadata.delete(fileId); + fileMetadata.set(fileId, metadata); + + while (fileMetadata.size > MAX_FILE_METADATA_ENTRIES) { + fileMetadata.delete(fileMetadata.keys().next().value!); + } + }, +}; diff --git a/src/trpc/routers/category.ts b/src/trpc/routers/category.ts index b148bf2..67dc95a 100644 --- a/src/trpc/routers/category.ts +++ b/src/trpc/routers/category.ts @@ -91,8 +91,12 @@ const categoryRouter = router({ encName: { ciphertext: input.name, iv: input.nameIv }, }); } catch (e) { - if (e instanceof IntegrityError && e.message === "Inactive MEK version") { - throw new TRPCError({ code: "BAD_REQUEST", message: e.message }); + if (e instanceof IntegrityError) { + if (e.message === "Inactive MEK version") { + throw new TRPCError({ code: "BAD_REQUEST", message: e.message }); + } else if (e.message === "Category not found") { + throw new TRPCError({ code: "NOT_FOUND", message: "Invalid parent category id" }); + } } throw e; } diff --git a/src/trpc/routers/directory.ts b/src/trpc/routers/directory.ts index 609d0ef..21f0fc6 100644 --- a/src/trpc/routers/directory.ts +++ b/src/trpc/routers/directory.ts @@ -89,8 +89,12 @@ const directoryRouter = router({ encName: { ciphertext: input.name, iv: input.nameIv }, }); } catch (e) { - if (e instanceof IntegrityError && e.message === "Inactive MEK version") { - throw new TRPCError({ code: "BAD_REQUEST", message: e.message }); + if (e instanceof IntegrityError) { + if (e.message === "Inactive MEK version") { + throw new TRPCError({ code: "BAD_REQUEST", message: e.message }); + } else if (e.message === "Directory not found") { + throw new TRPCError({ code: "NOT_FOUND", message: "Invalid parent directory id" }); + } } throw e; } diff --git a/src/trpc/routers/upload.ts b/src/trpc/routers/upload.ts index 992f2b6..99bb902 100644 --- a/src/trpc/routers/upload.ts +++ b/src/trpc/routers/upload.ts @@ -98,6 +98,8 @@ const uploadRouter = router({ throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid MEK version" }); } else if (e.message === "Inactive HSK version") { throw new TRPCError({ code: "BAD_REQUEST", message: "Invalid HSK version" }); + } else if (e.message === "Directory not found") { + throw new TRPCError({ code: "NOT_FOUND", message: "Invalid parent directory id" }); } } throw e; diff --git a/tests/helpers/database.ts b/tests/helpers/database.ts new file mode 100644 index 0000000..ea23330 --- /dev/null +++ b/tests/helpers/database.ts @@ -0,0 +1,84 @@ +import { sql } from "kysely"; +import db from "#lib/server/db/kysely.js"; + +export const resetDatabase = async () => { + const { rows } = await sql<{ tablename: string }>` + SELECT tablename FROM pg_tables + WHERE schemaname = 'public' AND tablename NOT LIKE 'kysely_%' + `.execute(db); + await sql`TRUNCATE ${sql.join(rows.map(({ tablename }) => sql.table(tablename)))} RESTART IDENTITY CASCADE`.execute( + db, + ); +}; + +let sequence = 0; + +const nextSequence = () => ++sequence; + +export const createUser = async () => { + const n = nextSequence(); + const { id: userId } = await db + .insertInto("user") + .values({ email: `user${n}@example.com`, nickname: `user${n}`, password: "password" }) + .returning("id") + .executeTakeFirstOrThrow(); + const { id: clientId } = await db + .insertInto("client") + .values({ encryption_public_key: `enc${n}`, signature_public_key: `sig${n}` }) + .returning("id") + .executeTakeFirstOrThrow(); + await db + .insertInto("user_client") + .values({ user_id: userId, client_id: clientId, state: "active" }) + .execute(); + await db + .insertInto("master_encryption_key") + .values({ user_id: userId, version: 1, state: "active" }) + .execute(); + return { userId, clientId }; +}; + +export const ciphertext = (value = "ciphertext") => ({ ciphertext: value, iv: "iv" }); + +export const createDirectory = async (userId: number, parentId: DirectoryId = "root") => { + const n = nextSequence(); + const { id } = await db + .insertInto("directory") + .values({ + parent_id: parentId === "root" ? null : parentId, + user_id: userId, + master_encryption_key_version: 1, + encrypted_data_encryption_key: `dek${n}`, + data_encryption_key_version: new Date(), + encrypted_name: ciphertext(), + }) + .returning("id") + .executeTakeFirstOrThrow(); + return id; +}; + +export const createCategory = async (userId: number, parentId: CategoryId = "root") => { + const n = nextSequence(); + const { id } = await db + .insertInto("category") + .values({ + parent_id: parentId === "root" ? null : parentId, + user_id: userId, + master_encryption_key_version: 1, + encrypted_data_encryption_key: `dek${n}`, + data_encryption_key_version: new Date(), + encrypted_name: ciphertext(), + }) + .returning("id") + .executeTakeFirstOrThrow(); + return id; +}; + +export const newEntryParams = (userId: number, parentId: number | "root") => ({ + userId, + parentId, + mekVersion: 1, + encDek: `dek${nextSequence()}`, + dekVersion: new Date(), + encName: ciphertext(), +}); diff --git a/tests/server/download.test.ts b/tests/server/download.test.ts new file mode 100644 index 0000000..6b4e1ee --- /dev/null +++ b/tests/server/download.test.ts @@ -0,0 +1,52 @@ +import { mkdtemp, writeFile } from "fs/promises"; +import { tmpdir } from "os"; +import { join } from "path"; +import { buffer } from "stream/consumers"; +import type { ReadableStream } from "stream/web"; +import { describe, expect, it } from "vitest"; +import { createEncContentStream } from "#lib/server/services/file.js"; + +const createFile = async (content: string) => { + const path = join(await mkdtemp(join(tmpdir(), "arkvault-test-")), "file"); + await writeFile(path, content); + return path; +}; + +const read = async (stream: unknown) => (await buffer(stream as ReadableStream)).toString(); + +describe("createEncContentStream", () => { + it("serves the whole content when no range is requested", async () => { + const path = await createFile("0123456789"); + + const { encContentStream, range, isRangeRequest } = await createEncContentStream(path); + + expect(isRangeRequest).toBe(false); + expect(range).toEqual({ start: 0, end: 9, total: 10 }); + expect(await read(encContentStream)).toBe("0123456789"); + }); + + it("serves the requested range", async () => { + const path = await createFile("0123456789"); + + const { encContentStream, range, isRangeRequest } = await createEncContentStream( + path, + undefined, + { start: 2, end: 5 }, + ); + + expect(isRangeRequest).toBe(true); + expect(range).toEqual({ start: 2, end: 5, total: 10 }); + expect(await read(encContentStream)).toBe("2345"); + }); + + it("prepends the legacy IV to the content", async () => { + const path = await createFile("6789"); + + const { encContentStream } = await createEncContentStream(path, Buffer.from("012345"), { + start: 4, + end: 7, + }); + + expect(await read(encContentStream)).toBe("4567"); + }); +}); diff --git a/tests/server/ownership.test.ts b/tests/server/ownership.test.ts new file mode 100644 index 0000000..08d1ac2 --- /dev/null +++ b/tests/server/ownership.test.ts @@ -0,0 +1,64 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import { CategoryRepo, DirectoryRepo, UploadRepo, IntegrityError } from "#lib/server/db/index.js"; +import { + createCategory, + createDirectory, + createUser, + newEntryParams, + resetDatabase, + ciphertext, +} from "../helpers/database"; + +beforeEach(resetDatabase); + +describe("parent ownership", () => { + it("allows creating a directory under the user's own directory", async () => { + const { userId } = await createUser(); + const parentId = await createDirectory(userId); + + await DirectoryRepo.registerDirectory(newEntryParams(userId, parentId)); + + const directories = await DirectoryRepo.getAllDirectoriesByParent(userId, parentId); + expect(directories).toHaveLength(1); + }); + + it("rejects creating a directory under another user's directory", async () => { + const { userId: victimId } = await createUser(); + const { userId: attackerId } = await createUser(); + const victimDirectoryId = await createDirectory(victimId); + + await expect( + DirectoryRepo.registerDirectory(newEntryParams(attackerId, victimDirectoryId)), + ).rejects.toThrow(new IntegrityError("Directory not found")); + }); + + it("rejects creating a category under another user's category", async () => { + const { userId: victimId } = await createUser(); + const { userId: attackerId } = await createUser(); + const victimCategoryId = await createCategory(victimId); + + await expect( + CategoryRepo.registerCategory(newEntryParams(attackerId, victimCategoryId)), + ).rejects.toThrow(new IntegrityError("Category not found")); + }); + + it("rejects uploading a file into another user's directory", async () => { + const { userId: victimId } = await createUser(); + const { userId: attackerId } = await createUser(); + const victimDirectoryId = await createDirectory(victimId); + + await expect( + UploadRepo.createFileUploadSession({ + ...newEntryParams(attackerId, victimDirectoryId), + id: crypto.randomUUID(), + path: "unused", + totalChunks: 1, + expiresAt: new Date(Date.now() + 60_000), + hskVersion: null, + contentType: "image/png", + encCreatedAt: null, + encLastModifiedAt: ciphertext(), + }), + ).rejects.toThrow(new IntegrityError("Directory not found")); + }); +}); diff --git a/tests/server/upload.test.ts b/tests/server/upload.test.ts new file mode 100644 index 0000000..320a730 --- /dev/null +++ b/tests/server/upload.test.ts @@ -0,0 +1,35 @@ +import { beforeEach, describe, expect, it } from "vitest"; +import { UploadRepo } from "#lib/server/db/index.js"; +import { isChunkUploaded } from "#lib/server/services/upload.js"; +import { ciphertext, createUser, newEntryParams, resetDatabase } from "../helpers/database"; + +beforeEach(resetDatabase); + +describe("upload session bitmap", () => { + it("matches the bit order of PostgreSQL's set_bit", async () => { + const { userId } = await createUser(); + const id = crypto.randomUUID(); + await UploadRepo.createFileUploadSession({ + ...newEntryParams(userId, "root"), + id, + path: "unused", + totalChunks: 20, + expiresAt: new Date(Date.now() + 60_000), + hskVersion: null, + contentType: "image/png", + encCreatedAt: null, + encLastModifiedAt: ciphertext(), + }); + + const uploaded = [1, 8, 9, 20]; + for (const chunkIndex of uploaded) { + await UploadRepo.markChunkAsUploaded(id, chunkIndex); + } + + const session = await UploadRepo.getUploadSession(id, userId); + expect(session?.uploadedChunks).toBe(uploaded.length); + for (let chunkIndex = 1; chunkIndex <= 20; chunkIndex++) { + expect(isChunkUploaded(session!.bitmap, chunkIndex)).toBe(uploaded.includes(chunkIndex)); + } + }); +}); diff --git a/tests/setup/database.ts b/tests/setup/database.ts new file mode 100644 index 0000000..142e385 --- /dev/null +++ b/tests/setup/database.ts @@ -0,0 +1,53 @@ +import { PostgreSqlContainer, type StartedPostgreSqlContainer } from "@testcontainers/postgresql"; +import { Kysely, PostgresDialect } from "kysely"; +import { Migrator } from "kysely/migration"; +import pg from "pg"; +import type { TestProject } from "vitest/node"; +import migrations from "../../src/lib/server/db/migrations"; + +declare module "vitest" { + export interface ProvidedContext { + database: { + host: string; + port: number; + user: string; + password: string; + name: string; + }; + } +} + +let container: StartedPostgreSqlContainer | undefined; + +export const setup = async (project: TestProject) => { + container = await new PostgreSqlContainer("postgres:17").start(); + + const database = { + host: container.getHost(), + port: container.getPort(), + user: container.getUsername(), + password: container.getPassword(), + name: container.getDatabase(), + }; + + const db = new Kysely({ + dialect: new PostgresDialect({ + pool: new pg.Pool({ ...database, database: database.name }), + }), + }); + try { + const { error } = await new Migrator({ + db, + provider: { getMigrations: async () => migrations }, + }).migrateToLatest(); + if (error) throw error; + } finally { + await db.destroy(); + } + + project.provide("database", database); +}; + +export const teardown = async () => { + await container?.stop(); +}; diff --git a/tests/setup/polyfills.ts b/tests/setup/polyfills.ts new file mode 100644 index 0000000..67743c9 --- /dev/null +++ b/tests/setup/polyfills.ts @@ -0,0 +1,13 @@ +// Node.js 22 doesn't support base64 conversion of Uint8Array yet (latest Chromium does) + +if (!Uint8Array.prototype.toBase64) { + Uint8Array.prototype.toBase64 = function () { + return Buffer.from(this.buffer, this.byteOffset, this.byteLength).toString("base64"); + }; +} + +if (!Uint8Array.fromBase64) { + Uint8Array.fromBase64 = (data: string) => { + return new Uint8Array(Buffer.from(data, "base64")); + }; +} diff --git a/tests/setup/server.ts b/tests/setup/server.ts new file mode 100644 index 0000000..6e27723 --- /dev/null +++ b/tests/setup/server.ts @@ -0,0 +1,20 @@ +import { mkdtemp } from "fs/promises"; +import { tmpdir } from "os"; +import { join } from "path"; +import { vi } from "vitest"; + +const storagePath = await mkdtemp(join(tmpdir(), "arkvault-test-")); + +vi.mock("#lib/server/loadenv.js", async () => { + const { inject } = await import("vitest"); + return { + default: { + nodeEnv: "test", + database: inject("database"), + session: { secret: "test-secret", exp: 14 * 24 * 3600 * 1000 }, + challenge: { userClientExp: 5 * 60 * 1000, sessionUpgradeExp: 5 * 60 * 1000 }, + libraryPath: join(storagePath, "library"), + thumbnailsPath: join(storagePath, "thumbnails"), + }, + }; +}); diff --git a/tests/unit/concurrency.test.ts b/tests/unit/concurrency.test.ts new file mode 100644 index 0000000..3079384 --- /dev/null +++ b/tests/unit/concurrency.test.ts @@ -0,0 +1,92 @@ +import { describe, expect, it } from "vitest"; +import { BoundedQueue, Scheduler } from "#lib/utils/concurrency/index.js"; + +const tick = () => new Promise((resolve) => setTimeout(resolve, 0)); + +describe("BoundedQueue", () => { + it("blocks producers while full", async () => { + const queue = new BoundedQueue(2); + await queue.push(1); + await queue.push(2); + + let isPushed = false; + const pushing = queue.push(3).then(() => (isPushed = true)); + await tick(); + expect(isPushed).toBe(false); + + expect(await queue.pop()).toBe(1); + await pushing; + expect(isPushed).toBe(true); + }); + + it("drains remaining items and then returns null after closing", async () => { + const queue = new BoundedQueue(2); + await queue.push(1); + queue.close(); + + expect(await queue.pop()).toBe(1); + expect(await queue.pop()).toBeNull(); + await expect(queue.push(2)).rejects.toThrow("Queue closed"); + }); + + it("wakes up waiting consumers when closed", async () => { + const queue = new BoundedQueue(1); + const popping = queue.pop(); + queue.close(); + expect(await popping).toBeNull(); + }); +}); + +describe("Scheduler", () => { + it("defers tasks while the memory limit is exceeded", async () => { + const scheduler = new Scheduler(100); + const events: string[] = []; + + let finishFirst!: () => void; + const first = scheduler.schedule(100, async () => { + events.push("first:start"); + await new Promise((resolve) => (finishFirst = resolve)); + events.push("first:end"); + }); + const second = scheduler.schedule(10, async () => { + events.push("second:start"); + }); + + await tick(); + expect(events).toEqual(["first:start"]); + + finishFirst(); + await Promise.all([first, second]); + expect(events).toEqual(["first:start", "first:end", "second:start"]); + }); + + it("runs small tasks concurrently", async () => { + const scheduler = new Scheduler(100); + let running = 0; + let maxRunning = 0; + + await Promise.all( + [1, 2, 3].map((value) => + scheduler.schedule(10, async () => { + maxRunning = Math.max(maxRunning, ++running); + await tick(); + running--; + return value; + }), + ), + ); + + expect(maxRunning).toBe(3); + }); + + it("releases memory when a task fails", async () => { + const scheduler = new Scheduler(100); + await expect( + scheduler.schedule(100, async () => { + throw new Error("Failed"); + }), + ).rejects.toThrow("Failed"); + + await expect(scheduler.schedule(100, async () => {})).resolves.toBeUndefined(); + }); +}); diff --git a/tests/unit/crypto.test.ts b/tests/unit/crypto.test.ts new file mode 100644 index 0000000..a29e8c6 --- /dev/null +++ b/tests/unit/crypto.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import { + decodeFromBase64, + decryptChunk, + decryptString, + encodeToBase64, + encryptChunk, + encryptString, + generateDataKey, +} from "#lib/modules/crypto/index.js"; + +describe("base64", () => { + it("round-trips large buffers", () => { + const data = Uint8Array.from({ length: 1024 * 1024 }, (_, i) => (i * 31) % 256); + const encoded = encodeToBase64(data); + expect(new Uint8Array(decodeFromBase64(encoded))).toEqual(data); + expect(encodeToBase64(data.buffer)).toBe(encoded); + }); +}); + +describe("AES-GCM", () => { + it("round-trips strings", async () => { + const { dataKey } = await generateDataKey(); + const { ciphertext, iv } = await encryptString("안녕하세요.jpg", dataKey); + expect(await decryptString(ciphertext, iv, dataKey)).toBe("안녕하세요.jpg"); + }); + + it("round-trips chunks", async () => { + const { dataKey } = await generateDataKey(); + const chunk = crypto.getRandomValues(new Uint8Array(4096)); + + const encrypted = await encryptChunk(chunk.buffer, dataKey); + + expect(encrypted.byteLength).toBe(4096 + 12 + 16); + expect(new Uint8Array(await decryptChunk(encrypted, dataKey))).toEqual(chunk); + }); + + it("rejects tampered chunks", async () => { + const { dataKey } = await generateDataKey(); + const encrypted = new Uint8Array(await encryptChunk(new Uint8Array(32).buffer, dataKey)); + encrypted[20]! ^= 1; + + await expect(decryptChunk(encrypted.buffer, dataKey)).rejects.toThrow(); + }); +}); diff --git a/tests/unit/range.test.ts b/tests/unit/range.test.ts new file mode 100644 index 0000000..002220e --- /dev/null +++ b/tests/unit/range.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "vitest"; +import { CHUNK_SIZE, ENCRYPTED_CHUNK_SIZE, ENCRYPTION_OVERHEAD } from "#lib/constants/index.js"; +import { getContentRangeHeader, parseRangeHeader } from "#lib/modules/http.js"; +import { getDecryptedSize, getEncryptedRange } from "../../src/service-worker/modules/crypto"; + +describe("parseRangeHeader", () => { + it("parses byte ranges", () => { + expect(parseRangeHeader(null)).toBeUndefined(); + expect(parseRangeHeader("bytes=0-")).toEqual({ start: 0, end: undefined }); + expect(parseRangeHeader("bytes=10-20")).toEqual({ start: 10, end: 20 }); + expect(parseRangeHeader("bytes=10-20, 30-40")).toEqual({ start: 10, end: 20 }); + }); + + it("formats Content-Range", () => { + expect(getContentRangeHeader({ start: 1, end: 2, total: 3 })).toEqual({ + "Content-Range": "bytes 1-2/3", + }); + }); +}); + +describe("encrypted chunk layout", () => { + const encryptedSizeOf = (size: number) => + size + Math.ceil(size / CHUNK_SIZE) * ENCRYPTION_OVERHEAD; + + it.each([1, CHUNK_SIZE - 1, CHUNK_SIZE, CHUNK_SIZE + 1, 3 * CHUNK_SIZE + 12345])( + "computes the decrypted size of %i bytes", + (size) => { + expect(getDecryptedSize(encryptedSizeOf(size), false)).toBe(size); + }, + ); + + it("computes the decrypted size of legacy files", () => { + expect(getDecryptedSize(100 + ENCRYPTION_OVERHEAD, true)).toBe(100); + }); + + it("maps a plaintext range to whole encrypted chunks", () => { + const totalSize = 3 * CHUNK_SIZE + 100; + const totalEncryptedSize = encryptedSizeOf(totalSize); + + expect(getEncryptedRange(0, 10, totalEncryptedSize, false)).toEqual({ + firstChunkIndex: 0, + lastChunkIndex: 0, + start: 0, + end: ENCRYPTED_CHUNK_SIZE - 1, + }); + expect(getEncryptedRange(CHUNK_SIZE - 1, CHUNK_SIZE, totalEncryptedSize, false)).toEqual({ + firstChunkIndex: 0, + lastChunkIndex: 1, + start: 0, + end: 2 * ENCRYPTED_CHUNK_SIZE - 1, + }); + expect(getEncryptedRange(totalSize - 1, totalSize - 1, totalEncryptedSize, false)).toEqual({ + firstChunkIndex: 3, + lastChunkIndex: 3, + start: 3 * ENCRYPTED_CHUNK_SIZE, + end: totalEncryptedSize - 1, + }); + }); + + it("maps any range of legacy files to the whole file", () => { + expect(getEncryptedRange(5, 10, 1000, true)).toEqual({ + firstChunkIndex: 0, + lastChunkIndex: 0, + start: 0, + end: 999, + }); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..1049122 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + projects: [ + { + extends: true, + test: { + name: "unit", + include: ["tests/unit/**/*.test.ts"], + setupFiles: ["tests/setup/polyfills.ts"], + }, + }, + { + extends: true, + test: { + name: "server", + include: ["tests/server/**/*.test.ts"], + globalSetup: ["tests/setup/database.ts"], + setupFiles: ["tests/setup/server.ts"], + // Test files share a single database + fileParallelism: false, + hookTimeout: 60_000, + }, + }, + ], + }, +});