Merge pull request #474 from DanTheMan827/vitest
feat(vitest): add vitest config and tests
This commit is contained in:
commit
b199844d07
13 changed files with 931 additions and 44 deletions
41
.github/workflows/tests.yml
vendored
Normal file
41
.github/workflows/tests.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
name: Run Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
pull_request:
|
||||
branches: ['*']
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
./bun_modules
|
||||
./node_modules
|
||||
./bun.lock
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Install playwright dependencies
|
||||
run: bun run install:playwright
|
||||
|
||||
- name: Run vitest
|
||||
run: bun run test:headless
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -9,6 +9,8 @@ dist
|
|||
.env
|
||||
# Neutralino
|
||||
.tmp/
|
||||
.vitest-attachments/
|
||||
**/__screenshots__/*
|
||||
bin/
|
||||
*.log
|
||||
.storage/
|
||||
|
|
|
|||
127
bun.lock
127
bun.lock
|
|
@ -17,6 +17,7 @@
|
|||
"@kawarp/core": "^1.1.1",
|
||||
"@svta/common-media-library": "^0.18.1",
|
||||
"@uimaxbai/am-lyrics": "^1.1.4",
|
||||
"@vitest/web-worker": "^4.1.2",
|
||||
"appwrite": "^23.0.0",
|
||||
"butterchurn": "^2.6.7",
|
||||
"butterchurn-presets": "^2.4.7",
|
||||
|
|
@ -36,17 +37,21 @@
|
|||
"svgo": "^4.0.1",
|
||||
"url-toolkit": "^2.2.5",
|
||||
"uuid": "^13.0.0",
|
||||
"vitest": "^4.1.2",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@capacitor/cli": "^8.2.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@types/node": "^25.3.5",
|
||||
"@vitest/browser-playwright": "^4.1.2",
|
||||
"eslint": "^9.39.3",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"formidable": "^3.5.4",
|
||||
"globals": "^17.4.0",
|
||||
"htmlhint": "^1.9.2",
|
||||
"miniflare": "^4.20260301.1",
|
||||
"playwright": "^1.58.2",
|
||||
"prettier": "^3.8.1",
|
||||
"stylelint": "^16.26.1",
|
||||
"stylelint-config-standard": "^39.0.1",
|
||||
|
|
@ -245,6 +250,8 @@
|
|||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@blazediff/core": ["@blazediff/core@1.9.1", "", {}, "sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA=="],
|
||||
|
||||
"@cacheable/memory": ["@cacheable/memory@2.0.8", "", { "dependencies": { "@cacheable/utils": "^2.4.0", "@keyv/bigmap": "^1.3.1", "hookified": "^1.15.1", "keyv": "^5.6.0" } }, "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw=="],
|
||||
|
||||
"@cacheable/utils": ["@cacheable/utils@2.4.1", "", { "dependencies": { "hashery": "^1.5.1", "keyv": "^5.6.0" } }, "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA=="],
|
||||
|
|
@ -481,6 +488,8 @@
|
|||
|
||||
"@paralleldrive/cuid2": ["@paralleldrive/cuid2@2.3.1", "", { "dependencies": { "@noble/hashes": "^1.1.5" } }, "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw=="],
|
||||
|
||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||
|
||||
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
|
||||
|
||||
"@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
|
||||
|
|
@ -553,10 +562,14 @@
|
|||
|
||||
"@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@surma/rollup-plugin-off-main-thread": ["@surma/rollup-plugin-off-main-thread@2.2.3", "", { "dependencies": { "ejs": "^3.1.6", "json5": "^2.2.0", "magic-string": "^0.25.0", "string.prototype.matchall": "^4.0.6" } }, "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ=="],
|
||||
|
||||
"@svta/common-media-library": ["@svta/common-media-library@0.18.1", "", {}, "sha512-VMj1jI8OWphurcozF+dezABUm9Mht6iAsSiKsFUKVT35fddOowvLoGz23Gx6lEHaAHkDy9o/aVi5s9DSp3K15Q=="],
|
||||
|
||||
"@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
|
||||
|
||||
"@trapezedev/gradle-parse": ["@trapezedev/gradle-parse@7.1.3", "", {}, "sha512-WQVF5pEJ5o/mUyvfGTG9nBKx9Te/ilKM3r2IT69GlbaooItT5ao7RyF1MUTBNjHLPk/xpGUY3c6PyVnjDlz0Vw=="],
|
||||
|
||||
"@trapezedev/project": ["@trapezedev/project@7.1.3", "", { "dependencies": { "@ionic/utils-fs": "^3.1.5", "@ionic/utils-subprocess": "^2.1.8", "@prettier/plugin-xml": "^2.2.0", "@trapezedev/gradle-parse": "7.1.3", "@xmldom/xmldom": "^0.7.5", "conventional-changelog": "^3.1.4", "cross-spawn": "^7.0.3", "diff": "^5.1.0", "env-paths": "^3.0.0", "gradle-to-js": "^2.0.0", "ini": "^2.0.0", "kleur": "^4.1.5", "lodash": "^4.17.21", "mergexml": "^1.2.3", "plist": "^3.0.4", "prettier": "^2.7.1", "prompts": "^2.4.2", "replace": "^1.1.0", "tempy": "^1.0.1", "tmp": "^0.2.1", "ts-node": "^10.2.1", "xcode": "^3.0.1", "xml-js": "^1.6.11", "xpath": "^0.0.32", "yargs": "^17.2.1" } }, "sha512-GANh8Ey73MechZrryfJoILY9hBnWqzS6AdB53zuWBCBbaiImyblXT41fWdN6pB2f5+cNI2FAUxGfVhl+LeEVbQ=="],
|
||||
|
|
@ -569,6 +582,12 @@
|
|||
|
||||
"@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="],
|
||||
|
||||
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
|
||||
|
||||
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
|
||||
|
||||
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/fs-extra": ["@types/fs-extra@8.1.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ=="],
|
||||
|
|
@ -591,6 +610,26 @@
|
|||
|
||||
"@uimaxbai/am-lyrics": ["@uimaxbai/am-lyrics@1.1.4", "", { "dependencies": { "@babel/runtime": "^7.27.6", "lit": "^3.1.4" }, "peerDependencies": { "@lit/react": "^1.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@lit/react", "react"] }, "sha512-LEwvbfgz6o71kYTq1vMlfou/powr8q4CJQWuyL2H48Dwo1/vH59SKiB3nz/WOEQ1S69uaSmfqf8Prtx6+ZNIrQ=="],
|
||||
|
||||
"@vitest/browser": ["@vitest/browser@4.1.2", "", { "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", "tinyrainbow": "^3.1.0", "ws": "^8.19.0" }, "peerDependencies": { "vitest": "4.1.2" } }, "sha512-CwdIf90LNf1Zitgqy63ciMAzmyb4oIGs8WZ40VGYrWkssQKeEKr32EzO8MKUrDPPcPVHFI9oQ5ni2Hp24NaNRQ=="],
|
||||
|
||||
"@vitest/browser-playwright": ["@vitest/browser-playwright@4.1.2", "", { "dependencies": { "@vitest/browser": "4.1.2", "@vitest/mocker": "4.1.2", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "playwright": "*", "vitest": "4.1.2" } }, "sha512-N0Z2HzMLvMR6k/tWPTS6Q/DaRscrkax/f2f9DIbNQr+Cd1l4W4wTf/I6S983PAMr0tNqqoTL+xNkLh9M5vbkLg=="],
|
||||
|
||||
"@vitest/expect": ["@vitest/expect@4.1.2", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ=="],
|
||||
|
||||
"@vitest/mocker": ["@vitest/mocker@4.1.2", "", { "dependencies": { "@vitest/spy": "4.1.2", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q=="],
|
||||
|
||||
"@vitest/pretty-format": ["@vitest/pretty-format@4.1.2", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA=="],
|
||||
|
||||
"@vitest/runner": ["@vitest/runner@4.1.2", "", { "dependencies": { "@vitest/utils": "4.1.2", "pathe": "^2.0.3" } }, "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ=="],
|
||||
|
||||
"@vitest/snapshot": ["@vitest/snapshot@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A=="],
|
||||
|
||||
"@vitest/spy": ["@vitest/spy@4.1.2", "", {}, "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA=="],
|
||||
|
||||
"@vitest/utils": ["@vitest/utils@4.1.2", "", { "dependencies": { "@vitest/pretty-format": "4.1.2", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ=="],
|
||||
|
||||
"@vitest/web-worker": ["@vitest/web-worker@4.1.2", "", { "dependencies": { "obug": "^2.1.1" }, "peerDependencies": { "vitest": "4.1.2" } }, "sha512-P5XxkZuiVcN8NGePi53VS3gJUHu3J0luyPPuWBirZB3d6Tjfqy4+AW+3vCQTMr5KnEfZXBmEXWh3o9K58bSoAw=="],
|
||||
|
||||
"@xml-tools/parser": ["@xml-tools/parser@1.0.11", "", { "dependencies": { "chevrotain": "7.1.1" } }, "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA=="],
|
||||
|
||||
"@xmldom/xmldom": ["@xmldom/xmldom@0.7.13", "", {}, "sha512-lm2GW5PkosIzccsaZIz7tp8cPADSIlIHWDFTR1N0SzfinhhYgeIQjFMz4rYzanCScr3DqQLeomUDArp6MWKm+g=="],
|
||||
|
|
@ -611,7 +650,7 @@
|
|||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
"ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"appwrite": ["appwrite@23.0.0", "", { "dependencies": { "json-bigint": "1.0.0" } }, "sha512-K11a597npl3jsnxWKzjw163n4GguH4+/zBCOiU15yc1u+7QF0nP9mxsY4JxKrBU6bmQRtgtMTPv/6YOLSwp/QQ=="],
|
||||
|
||||
|
|
@ -619,6 +658,8 @@
|
|||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
|
||||
|
||||
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
|
||||
|
||||
"array-ify": ["array-ify@1.0.0", "", {}, "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng=="],
|
||||
|
|
@ -631,6 +672,8 @@
|
|||
|
||||
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
|
||||
|
||||
"assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
|
||||
|
||||
"astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
|
@ -715,6 +758,8 @@
|
|||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="],
|
||||
|
||||
"chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"chevrotain": ["chevrotain@7.1.1", "", { "dependencies": { "regexp-to-ast": "0.5.0" } }, "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw=="],
|
||||
|
|
@ -843,6 +888,8 @@
|
|||
|
||||
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="],
|
||||
|
|
@ -851,6 +898,8 @@
|
|||
|
||||
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
|
||||
|
||||
"dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
|
@ -889,6 +938,8 @@
|
|||
|
||||
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
|
||||
|
||||
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||
|
||||
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||
|
|
@ -917,7 +968,7 @@
|
|||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="],
|
||||
"estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
|
|
@ -929,6 +980,8 @@
|
|||
|
||||
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
|
||||
|
||||
"expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
|
||||
|
|
@ -975,7 +1028,7 @@
|
|||
|
||||
"fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
"fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
|
|
@ -1241,7 +1294,9 @@
|
|||
|
||||
"lucide-static": ["lucide-static@0.577.0", "", {}, "sha512-hx39J5Tq4JWF2ALY+5YRg+SxQLpeAmLJDXNcqiBJH/UuVwp43it9fyki/onZO7AVFgG5ZbB+fWwZR9mwGHE2XQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
|
||||
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
|
||||
|
||||
|
|
@ -1285,6 +1340,8 @@
|
|||
|
||||
"modify-values": ["modify-values@1.0.1", "", {}, "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw=="],
|
||||
|
||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||
|
||||
"ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
|
@ -1325,6 +1382,8 @@
|
|||
|
||||
"object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="],
|
||||
|
||||
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
|
||||
|
||||
"on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="],
|
||||
|
||||
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||
|
|
@ -1361,6 +1420,8 @@
|
|||
|
||||
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
|
@ -1369,8 +1430,14 @@
|
|||
|
||||
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
|
||||
|
||||
"playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="],
|
||||
|
||||
"playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="],
|
||||
|
||||
"plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
|
||||
|
||||
"pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
|
||||
|
||||
"pocketbase": ["pocketbase@0.26.8", "", {}, "sha512-aQ/ewvS7ncvAE8wxoW10iAZu6ElgbeFpBhKPnCfvRovNzm2gW8u/sQNPGN6vNgVEagz44kK//C61oKjfa+7Low=="],
|
||||
|
||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||
|
|
@ -1397,6 +1464,8 @@
|
|||
|
||||
"pretty-bytes": ["pretty-bytes@6.1.1", "", {}, "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ=="],
|
||||
|
||||
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||
|
||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||
|
||||
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
|
||||
|
|
@ -1415,6 +1484,8 @@
|
|||
|
||||
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
|
||||
|
||||
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||
|
||||
"read-pkg": ["read-pkg@3.0.0", "", { "dependencies": { "load-json-file": "^4.0.0", "normalize-package-data": "^2.3.2", "path-type": "^3.0.0" } }, "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA=="],
|
||||
|
||||
"read-pkg-up": ["read-pkg-up@3.0.0", "", { "dependencies": { "find-up": "^2.0.0", "read-pkg": "^3.0.0" } }, "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw=="],
|
||||
|
|
@ -1499,6 +1570,8 @@
|
|||
|
||||
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||
|
||||
"siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
|
||||
|
||||
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
|
||||
|
|
@ -1511,6 +1584,8 @@
|
|||
|
||||
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
|
||||
|
||||
"sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="],
|
||||
|
||||
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
|
||||
|
||||
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
|
||||
|
|
@ -1539,6 +1614,10 @@
|
|||
|
||||
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
|
||||
|
||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||
|
||||
"std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="],
|
||||
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"stream-buffers": ["stream-buffers@2.2.0", "", {}, "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg=="],
|
||||
|
|
@ -1615,12 +1694,20 @@
|
|||
|
||||
"through2": ["through2@4.0.2", "", { "dependencies": { "readable-stream": "3" } }, "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw=="],
|
||||
|
||||
"tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
|
||||
|
||||
"tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="],
|
||||
|
||||
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
|
||||
|
|
@ -1693,6 +1780,8 @@
|
|||
|
||||
"vite-plugin-pwa": ["vite-plugin-pwa@1.2.0", "", { "dependencies": { "debug": "^4.3.6", "pretty-bytes": "^6.1.1", "tinyglobby": "^0.2.10", "workbox-build": "^7.4.0", "workbox-window": "^7.4.0" }, "peerDependencies": { "@vite-pwa/assets-generator": "^1.0.0", "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@vite-pwa/assets-generator"] }, "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw=="],
|
||||
|
||||
"vitest": ["vitest@4.1.2", "", { "dependencies": { "@vitest/expect": "4.1.2", "@vitest/mocker": "4.1.2", "@vitest/pretty-format": "4.1.2", "@vitest/runner": "4.1.2", "@vitest/snapshot": "4.1.2", "@vitest/spy": "4.1.2", "@vitest/utils": "4.1.2", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.2", "@vitest/browser-preview": "4.1.2", "@vitest/browser-webdriverio": "4.1.2", "@vitest/ui": "4.1.2", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
|
||||
|
|
@ -1709,6 +1798,8 @@
|
|||
|
||||
"which-typed-array": ["which-typed-array@1.1.20", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg=="],
|
||||
|
||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
||||
|
|
@ -1881,26 +1972,36 @@
|
|||
|
||||
"@rollup/plugin-node-resolve/@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
|
||||
|
||||
"@rollup/plugin-replace/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
|
||||
|
||||
"@rollup/plugin-replace/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="],
|
||||
|
||||
"@rollup/pluginutils/@types/estree": ["@types/estree@0.0.39", "", {}, "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw=="],
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@1.0.1", "", {}, "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg=="],
|
||||
|
||||
"@rollup/pluginutils/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"@rollup/pluginutils/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="],
|
||||
|
||||
"@surma/rollup-plugin-off-main-thread/magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="],
|
||||
|
||||
"@trapezedev/project/@ionic/utils-subprocess": ["@ionic/utils-subprocess@2.1.14", "", { "dependencies": { "@ionic/utils-array": "2.1.6", "@ionic/utils-fs": "3.1.7", "@ionic/utils-process": "2.1.11", "@ionic/utils-stream": "3.1.6", "@ionic/utils-terminal": "2.3.4", "cross-spawn": "^7.0.3", "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-nGYvyGVjU0kjPUcSRFr4ROTraT3w/7r502f5QJEsMRKTqa4eEzCshtwRk+/mpASm0kgBN5rrjYA5A/OZg8ahqg=="],
|
||||
|
||||
"@trapezedev/project/env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
|
||||
|
||||
"@trapezedev/project/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="],
|
||||
|
||||
"@vitest/browser/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
|
||||
|
||||
"babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
"brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"cacheable/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
|
||||
|
||||
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"conventional-changelog-writer/meow": ["meow@8.1.2", "", { "dependencies": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", "decamelize-keys": "^1.1.0", "hard-rejection": "^2.1.0", "minimist-options": "4.1.0", "normalize-package-data": "^3.0.0", "read-pkg-up": "^7.0.1", "redent": "^3.0.0", "trim-newlines": "^3.0.0", "type-fest": "^0.18.0", "yargs-parser": "^20.2.3" } }, "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q=="],
|
||||
|
||||
"conventional-changelog-writer/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
|
@ -2281,10 +2382,14 @@
|
|||
|
||||
"replace/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
||||
|
||||
"rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="],
|
||||
|
||||
"simple-swizzle/is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
|
||||
|
||||
"slice-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"stylelint/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"stylelint/file-entry-cache": ["file-entry-cache@11.1.2", "", { "dependencies": { "flat-cache": "^6.1.20" } }, "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log=="],
|
||||
|
|
@ -2299,6 +2404,8 @@
|
|||
|
||||
"ts-node/diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="],
|
||||
|
||||
"vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"vite-plugin-pwa/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"workbox-build/ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
|
@ -2313,6 +2420,8 @@
|
|||
|
||||
"workbox-build/tempy": ["tempy@0.6.0", "", { "dependencies": { "is-stream": "^2.0.0", "temp-dir": "^2.0.0", "type-fest": "^0.16.0", "unique-string": "^2.0.0" } }, "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"write-file-atomic/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||
|
||||
"xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
|
||||
|
|
@ -2365,8 +2474,14 @@
|
|||
|
||||
"@ionic/utils-terminal/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"@rollup/plugin-babel/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"@rollup/plugin-node-resolve/@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@rollup/plugin-replace/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"@rollup/pluginutils/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"@trapezedev/project/@ionic/utils-subprocess/@ionic/utils-process": ["@ionic/utils-process@2.1.11", "", { "dependencies": { "@ionic/utils-object": "2.1.6", "@ionic/utils-terminal": "2.3.4", "debug": "^4.0.0", "signal-exit": "^3.0.3", "tree-kill": "^1.2.2", "tslib": "^2.0.1" } }, "sha512-Uavxn+x8j3rDlZEk1X7YnaN6wCgbCwYQOeIjv/m94i1dzslqWhqIHEqxEyeE8HsT5Negboagg7GtQiABy+BLbA=="],
|
||||
|
||||
"@trapezedev/project/@ionic/utils-subprocess/@ionic/utils-stream": ["@ionic/utils-stream@3.1.6", "", { "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-4+Kitey1lTA1yGtnigeYNhV/0tggI3lWBMjC7tBs1K9GXa/q7q4CtOISppdh8QgtOhrhAXS2Igp8rbko/Cj+lA=="],
|
||||
|
|
@ -2471,6 +2586,8 @@
|
|||
|
||||
"workbox-build/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"workbox-build/rollup/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"@capacitor/assets/@capacitor/cli/@ionic/utils-subprocess/@ionic/utils-process": ["@ionic/utils-process@2.1.11", "", { "dependencies": { "@ionic/utils-object": "2.1.6", "@ionic/utils-terminal": "2.3.4", "debug": "^4.0.0", "signal-exit": "^3.0.3", "tree-kill": "^1.2.2", "tslib": "^2.0.1" } }, "sha512-Uavxn+x8j3rDlZEk1X7YnaN6wCgbCwYQOeIjv/m94i1dzslqWhqIHEqxEyeE8HsT5Negboagg7GtQiABy+BLbA=="],
|
||||
|
||||
"@capacitor/assets/@capacitor/cli/@ionic/utils-subprocess/@ionic/utils-stream": ["@ionic/utils-stream@3.1.6", "", { "dependencies": { "debug": "^4.0.0", "tslib": "^2.0.1" } }, "sha512-4+Kitey1lTA1yGtnigeYNhV/0tggI3lWBMjC7tBs1K9GXa/q7q4CtOISppdh8QgtOhrhAXS2Igp8rbko/Cj+lA=="],
|
||||
|
|
@ -2585,6 +2702,8 @@
|
|||
|
||||
"replace/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
|
||||
|
||||
"replace/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"replace/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||
|
||||
"workbox-build/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
|
|
|||
199
js/HiFi.test.ts
Normal file
199
js/HiFi.test.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import { expect, suite, test } from 'vitest';
|
||||
import { HiFiClient, TidalResponse } from './HiFi';
|
||||
|
||||
const ARTIST_ID = 3523908; // deadmau5
|
||||
const ALBUM_ID = 433360012; // deadmau5 - 4x4=12
|
||||
const ALBUM_ATMOS = 463900719; // Taylor Swift - The Life of a Showgirl
|
||||
const TRACK_ATMOS = 463900720; // Taylor Swift - The Fate of Ophelia
|
||||
const TRACK_NO_LOSSLESS = 31097959; // deadmau5 - while(1<2)
|
||||
const TRACK_VIDEO = 466464180; // Taylow Swift - The Fate of Ophelia
|
||||
const TRACK_LOSSLESS = 31097949; // deadmau5 - Avaritia
|
||||
const PLAYLIST_ID = '36ea71a8-445e-41a4-82ab-6628c581535d'; // Pop Hits
|
||||
|
||||
const instance = new HiFiClient();
|
||||
await instance.fetchToken();
|
||||
|
||||
function checkVersion({ version }: { version?: string }) {
|
||||
expect(version).toBeTypeOf('string');
|
||||
expect(version).not.equals('');
|
||||
expect(version).equals(HiFiClient.API_VERSION);
|
||||
}
|
||||
|
||||
async function getJson(res: Response | Promise<Response>) {
|
||||
res = await res;
|
||||
expect(res).toBeInstanceOf(Response);
|
||||
expect(res.ok).toBeTruthy();
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
async function checkRoute(
|
||||
route: string,
|
||||
routeResult: () => Promise<any>,
|
||||
checks: (data: any) => Promise<void>,
|
||||
mainKey: string | null = 'data'
|
||||
) {
|
||||
const routeData = await instance.query(route);
|
||||
const routeRes = await routeResult();
|
||||
expect(routeData).toBeInstanceOf(TidalResponse);
|
||||
expect(routeData).toEqual(routeRes);
|
||||
|
||||
const json = await routeData.json();
|
||||
checkVersion(json);
|
||||
|
||||
if (mainKey != null) {
|
||||
expect(json).toHaveProperty(mainKey);
|
||||
expect(json[mainKey]).not.toBeUndefined();
|
||||
}
|
||||
|
||||
await checks(json);
|
||||
}
|
||||
|
||||
test('Get token', async () => {
|
||||
const instance = new HiFiClient();
|
||||
|
||||
const token = await instance.fetchToken();
|
||||
expect(token).toBeTypeOf('string');
|
||||
expect(token).not.toBeUndefined();
|
||||
expect(token).not.length(0);
|
||||
expect(token).equals(instance.token);
|
||||
|
||||
const token2 = await instance.fetchToken(true);
|
||||
expect(token2).toBeTypeOf('string');
|
||||
expect(token2).not.toBeUndefined();
|
||||
expect(token2).not.length(0);
|
||||
expect(token2).equals(instance.token);
|
||||
expect(token2).not.equals(token);
|
||||
|
||||
expect(instance.appTokenExpiry).toBeGreaterThan(Date.now());
|
||||
});
|
||||
|
||||
test('Fetch atmos track info', async () => {
|
||||
await checkRoute(
|
||||
`/info/?id=${TRACK_ATMOS}`,
|
||||
() => instance.getInfo(TRACK_ATMOS),
|
||||
async (info) => {
|
||||
expect(info.data.audioModes).toContain('DOLBY_ATMOS');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('Fetch track', async () => {
|
||||
await checkRoute(
|
||||
`/track/?id=${TRACK_LOSSLESS}`,
|
||||
() => instance.getTrack(TRACK_LOSSLESS),
|
||||
async (track) => {
|
||||
expect(track.data.trackId).toBe(TRACK_LOSSLESS);
|
||||
expect(track.data.assetPresentation).toBeTypeOf('string');
|
||||
expect(track.data.audioQuality).toBeTypeOf('string');
|
||||
expect(track.data.manifestMimeType).toBeTypeOf('string');
|
||||
expect(track.data.manifestHash).toBeTypeOf('string');
|
||||
expect(track.data.manifest).toBeTypeOf('string');
|
||||
expect(track.data.albumReplayGain).toBeTypeOf('number');
|
||||
expect(track.data.albumPeakAmplitude).toBeTypeOf('number');
|
||||
expect(track.data.trackReplayGain).toBeTypeOf('number');
|
||||
expect(track.data.trackPeakAmplitude).toBeTypeOf('number');
|
||||
expect(track.data.bitDepth).toBeTypeOf('number');
|
||||
expect(track.data.sampleRate).toBeTypeOf('number');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test.skipIf(!instance.refreshToken)('Fetch recommendations', async () => {
|
||||
await checkRoute(
|
||||
`/recommendations/?id=${ARTIST_ID}`,
|
||||
() => instance.getRecommendations(ARTIST_ID),
|
||||
async (rec) => {}
|
||||
);
|
||||
});
|
||||
|
||||
test('Fetch similar artists', async () => {
|
||||
await checkRoute(
|
||||
`/artist/similar/?id=${ARTIST_ID}`,
|
||||
() => instance.getSimilarArtists(ARTIST_ID),
|
||||
async (rec) => {},
|
||||
'artists'
|
||||
);
|
||||
});
|
||||
|
||||
test('Fetch similar albums', async () => {
|
||||
await checkRoute(
|
||||
`/album/similar/?id=${ALBUM_ID}`,
|
||||
() => instance.getSimilarAlbums(ALBUM_ID),
|
||||
async (rec) => {},
|
||||
'albums'
|
||||
);
|
||||
});
|
||||
|
||||
test('Fetch artist info', async () => {
|
||||
await checkRoute(
|
||||
`/artist/?id=${ARTIST_ID}`,
|
||||
() => instance.getArtist(ARTIST_ID),
|
||||
async (info) => {
|
||||
expect(info).toHaveProperty('cover');
|
||||
expect(info.cover).not.toBeUndefined();
|
||||
},
|
||||
'artist'
|
||||
);
|
||||
});
|
||||
|
||||
test('Search', async () => {
|
||||
const query = 'deadmau5';
|
||||
await checkRoute(
|
||||
`/search/?q=${encodeURIComponent(query)}`,
|
||||
() =>
|
||||
instance.search({
|
||||
q: query,
|
||||
}),
|
||||
async (res) => {}
|
||||
);
|
||||
});
|
||||
|
||||
test('Fetch album info', async () => {
|
||||
await checkRoute(
|
||||
`/album/?id=${ALBUM_ID}`,
|
||||
() => instance.getAlbum(ALBUM_ID),
|
||||
async (info) => {
|
||||
expect(info.data).toHaveProperty('cover');
|
||||
expect(info.data.cover).not.toBeUndefined();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('Fetch playlist info', async () => {
|
||||
await checkRoute(
|
||||
`/playlist/?id=${PLAYLIST_ID}`,
|
||||
() => instance.getPlaylist(PLAYLIST_ID),
|
||||
async (info) => {
|
||||
expect(info.playlist).toHaveProperty('image');
|
||||
expect(info.playlist.image).not.toBeUndefined();
|
||||
},
|
||||
'playlist'
|
||||
);
|
||||
});
|
||||
|
||||
test.skipIf(!instance.refreshToken)('Fetch lyrics ', async () => {
|
||||
await checkRoute(
|
||||
`/lyrics/?id=${TRACK_ATMOS}`,
|
||||
() => instance.getLyrics(TRACK_ATMOS),
|
||||
async (info) => {},
|
||||
'lyrics'
|
||||
);
|
||||
});
|
||||
|
||||
test('Fetch video ', async () => {
|
||||
await checkRoute(
|
||||
`/video/?id=${TRACK_VIDEO}`,
|
||||
() => instance.getVideo(TRACK_VIDEO),
|
||||
async (info) => {},
|
||||
'video'
|
||||
);
|
||||
});
|
||||
|
||||
test('Fetch track manifests ', async () => {
|
||||
await checkRoute(
|
||||
`/trackManifests/?id=${TRACK_LOSSLESS}`,
|
||||
() => instance.getTrackManifest(TRACK_LOSSLESS),
|
||||
async (info) => {},
|
||||
'data'
|
||||
);
|
||||
});
|
||||
80
js/HiFi.ts
80
js/HiFi.ts
|
|
@ -1,9 +1,5 @@
|
|||
import { EventEmitter } from 'events';
|
||||
|
||||
const API_VERSION = '2.7';
|
||||
const BROWSER_CLIENT_ID = 'txNoH4kkV41MfH25';
|
||||
const BROWSER_CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
|
||||
|
||||
type Params = Record<string, string | number | undefined | null>;
|
||||
|
||||
class ResponseError extends Error {
|
||||
|
|
@ -37,6 +33,10 @@ export enum HiFiClientEvents {
|
|||
}
|
||||
|
||||
class HiFiClient {
|
||||
static readonly API_VERSION = '2.7';
|
||||
static readonly BROWSER_CLIENT_ID = 'txNoH4kkV41MfH25';
|
||||
static readonly BROWSER_CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
|
||||
|
||||
static #instance: HiFiClient | null = null;
|
||||
static get instance() {
|
||||
if (!HiFiClient.#instance) {
|
||||
|
|
@ -158,8 +158,8 @@ class HiFiClient {
|
|||
}
|
||||
|
||||
async #fetchAppToken({
|
||||
clientId = BROWSER_CLIENT_ID,
|
||||
clientSecret = BROWSER_CLIENT_SECRET,
|
||||
clientId = HiFiClient.BROWSER_CLIENT_ID,
|
||||
clientSecret = HiFiClient.BROWSER_CLIENT_SECRET,
|
||||
refreshToken,
|
||||
scope = 'r_usr+w_usr+w_sub',
|
||||
signal = new AbortController().signal,
|
||||
|
|
@ -218,8 +218,8 @@ class HiFiClient {
|
|||
static #getOptions({
|
||||
countryCode = 'US',
|
||||
baseUrl = null,
|
||||
clientId = BROWSER_CLIENT_ID,
|
||||
clientSecret = BROWSER_CLIENT_SECRET,
|
||||
clientId = HiFiClient.BROWSER_CLIENT_ID,
|
||||
clientSecret = HiFiClient.BROWSER_CLIENT_SECRET,
|
||||
token,
|
||||
tokenExpiry,
|
||||
refreshToken: tokenRefresh,
|
||||
|
|
@ -228,6 +228,16 @@ class HiFiClient {
|
|||
return { countryCode, baseUrl, clientId, clientSecret, token, tokenExpiry, tokenRefresh, storage };
|
||||
}
|
||||
|
||||
async fetchToken(force: boolean = false, signal: AbortSignal | undefined = undefined) {
|
||||
return await this.#fetchAppToken({
|
||||
clientId: this.#clientId,
|
||||
clientSecret: this.#clientSecret,
|
||||
signal,
|
||||
refreshToken: this.refreshToken || undefined,
|
||||
force: !!force,
|
||||
});
|
||||
}
|
||||
|
||||
async #fetchAuthenticated(
|
||||
url: string,
|
||||
params?: Params | URLSearchParams,
|
||||
|
|
@ -334,7 +344,7 @@ class HiFiClient {
|
|||
async getInfo(id: number, signal?: AbortSignal) {
|
||||
const url = `https://api.tidal.com/v1/tracks/${id}/`;
|
||||
const data = await this.#fetchJson(url, { countryCode: this.#countryCode }, signal);
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, data });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data });
|
||||
}
|
||||
|
||||
async getTrack(id: number, quality = 'HI_RES_LOSSLESS', immersiveAudio: boolean = false, signal?: AbortSignal) {
|
||||
|
|
@ -347,7 +357,7 @@ class HiFiClient {
|
|||
immersiveAudio: String(immersiveAudio),
|
||||
};
|
||||
const data = await this.#fetchJson(url, params, signal);
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, data });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data });
|
||||
}
|
||||
|
||||
async getTrackManifest(
|
||||
|
|
@ -382,7 +392,7 @@ class HiFiClient {
|
|||
drmData.certificateUrl = url;
|
||||
}
|
||||
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, data: res });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: res });
|
||||
}
|
||||
|
||||
async getWidevine() {
|
||||
|
|
@ -392,7 +402,7 @@ class HiFiClient {
|
|||
async getRecommendations(id: number, signal?: AbortSignal) {
|
||||
const url = `https://api.tidal.com/v1/tracks/${id}/recommendations`;
|
||||
const data = await this.#fetchJson(url, { limit: '20', countryCode: this.#countryCode }, signal);
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, data });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data });
|
||||
}
|
||||
|
||||
async getSimilarArtists(id: number, cursor?: string | number | null, signal?: AbortSignal) {
|
||||
|
|
@ -436,7 +446,10 @@ class HiFiClient {
|
|||
};
|
||||
};
|
||||
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, artists: (payload?.data || []).map(resolveArtist) });
|
||||
return HiFiClient.#jsonResponse({
|
||||
version: HiFiClient.API_VERSION,
|
||||
artists: (payload?.data || []).map(resolveArtist),
|
||||
});
|
||||
}
|
||||
|
||||
async getSimilarAlbums(id: number, cursor?: string | number | null, signal?: AbortSignal) {
|
||||
|
|
@ -497,7 +510,10 @@ class HiFiClient {
|
|||
};
|
||||
};
|
||||
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, albums: (payload?.data || []).map(resolveAlbum) });
|
||||
return HiFiClient.#jsonResponse({
|
||||
version: HiFiClient.API_VERSION,
|
||||
albums: (payload?.data || []).map(resolveAlbum),
|
||||
});
|
||||
}
|
||||
|
||||
async getArtist(
|
||||
|
|
@ -530,7 +546,7 @@ class HiFiClient {
|
|||
};
|
||||
}
|
||||
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, artist: artist_data, cover });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, artist: artist_data, cover });
|
||||
}
|
||||
|
||||
// f provided -> gather albums and optionally tracks
|
||||
|
|
@ -584,10 +600,11 @@ class HiFiClient {
|
|||
}
|
||||
}
|
||||
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, albums: page_data, tracks: top_tracks });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, albums: page_data, tracks: top_tracks });
|
||||
}
|
||||
|
||||
if (!album_ids.length) return HiFiClient.#jsonResponse({ version: API_VERSION, albums: page_data, tracks: [] });
|
||||
if (!album_ids.length)
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, albums: page_data, tracks: [] });
|
||||
|
||||
const fetchAlbumTracks = async (album_id: number) => {
|
||||
return await this.#withAlbumTrackSlot(async () => {
|
||||
|
|
@ -613,7 +630,7 @@ class HiFiClient {
|
|||
if (Array.isArray(t)) tracks.push(...t);
|
||||
}
|
||||
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, albums: page_data, tracks });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, albums: page_data, tracks });
|
||||
}
|
||||
|
||||
#buildCoverEntry(cover_slug: string, name?: string | null, track_id?: number | null) {
|
||||
|
|
@ -640,7 +657,7 @@ class HiFiClient {
|
|||
const cover_slug = album.cover;
|
||||
if (!cover_slug) throw new ResponseError(404, 'Cover not found');
|
||||
const entry = this.#buildCoverEntry(cover_slug, album.title || track_data.title, album.id || id);
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, covers: [entry] });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, covers: [entry] });
|
||||
}
|
||||
|
||||
const search_data = await this.#fetchJson(
|
||||
|
|
@ -658,7 +675,7 @@ class HiFiClient {
|
|||
covers.push(this.#buildCoverEntry(cover_slug, track.title, track.id));
|
||||
}
|
||||
if (!covers.length) throw new ResponseError(404, 'Cover not found');
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, covers });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, covers });
|
||||
}
|
||||
|
||||
async search(
|
||||
|
|
@ -690,7 +707,7 @@ class HiFiClient {
|
|||
},
|
||||
signal
|
||||
);
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, data: res });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: res });
|
||||
} catch (err: any) {
|
||||
if (err.status && ![400, 404].includes(err.status)) throw err;
|
||||
// fallback to text search
|
||||
|
|
@ -705,7 +722,7 @@ class HiFiClient {
|
|||
},
|
||||
signal
|
||||
);
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, data: fallback });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: fallback });
|
||||
}
|
||||
|
||||
const mapping: Array<[string | undefined, string, Params]> = [
|
||||
|
|
@ -746,7 +763,7 @@ class HiFiClient {
|
|||
for (const [val, url, params] of mapping) {
|
||||
if (val) {
|
||||
const data = await this.#fetchJson(url, params, signal);
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, data });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -783,7 +800,7 @@ class HiFiClient {
|
|||
if (Array.isArray(pageItems)) allItems.push(...pageItems);
|
||||
}
|
||||
albumData.items = allItems;
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, data: albumData });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, data: albumData });
|
||||
}
|
||||
|
||||
async getMix(id: string, signal?: AbortSignal) {
|
||||
|
|
@ -803,7 +820,7 @@ class HiFiClient {
|
|||
}
|
||||
}
|
||||
return HiFiClient.#jsonResponse({
|
||||
version: API_VERSION,
|
||||
version: HiFiClient.API_VERSION,
|
||||
mix: header,
|
||||
items: items.map((it: any) => (it.item ? it.item : it)),
|
||||
});
|
||||
|
|
@ -817,7 +834,7 @@ class HiFiClient {
|
|||
this.#fetchJson(itemsUrl, { countryCode: this.#countryCode, limit, offset }, signal),
|
||||
]);
|
||||
const items = (itemsData && itemsData.items) || itemsData;
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, playlist: playlistData, items });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, playlist: playlistData, items });
|
||||
}
|
||||
|
||||
// simplified artist/cover/lyrics/video/topvideos/similar methods (same pattern)
|
||||
|
|
@ -833,7 +850,7 @@ class HiFiClient {
|
|||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, lyrics: data });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, lyrics: data });
|
||||
}
|
||||
|
||||
async getVideo(id: number, quality = 'HIGH', mode = 'STREAM', presentation = 'FULL', signal?: AbortSignal) {
|
||||
|
|
@ -843,7 +860,7 @@ class HiFiClient {
|
|||
{ videoquality: quality, playbackmode: mode, assetpresentation: presentation },
|
||||
signal
|
||||
);
|
||||
return HiFiClient.#jsonResponse({ version: API_VERSION, video: data });
|
||||
return HiFiClient.#jsonResponse({ version: HiFiClient.API_VERSION, video: data });
|
||||
}
|
||||
|
||||
async getTopVideos(
|
||||
|
|
@ -867,7 +884,7 @@ class HiFiClient {
|
|||
}
|
||||
}
|
||||
return HiFiClient.#jsonResponse({
|
||||
version: API_VERSION,
|
||||
version: HiFiClient.API_VERSION,
|
||||
videos: videos.slice(offset, offset + limit),
|
||||
total: videos.length,
|
||||
});
|
||||
|
|
@ -886,7 +903,10 @@ class HiFiClient {
|
|||
switch (pathname) {
|
||||
case '/':
|
||||
return new TidalResponse(
|
||||
HiFiClient.#jsonResponse({ version: API_VERSION, Repo: 'https://github.com/binimum/hifi-api' })
|
||||
HiFiClient.#jsonResponse({
|
||||
version: HiFiClient.API_VERSION,
|
||||
Repo: 'https://github.com/binimum/hifi-api',
|
||||
})
|
||||
);
|
||||
case '/info':
|
||||
return new TidalResponse(await this.getInfo(Number(qp.id)));
|
||||
|
|
|
|||
457
js/api.test.ts
Normal file
457
js/api.test.ts
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
import { expect, test, suite, vi } from 'vitest';
|
||||
import { apiSettings, preferDolbyAtmosSettings, losslessContainerSettings } from './storage.js';
|
||||
import { MusicAPI } from './music-api.js';
|
||||
import { LyricsManager } from './lyrics.js';
|
||||
import type { LosslessAPI } from './api.js';
|
||||
import { HiFiClient } from './HiFi.js';
|
||||
import { FileRef } from '!/@dantheman827/taglib-ts/src/fileRef.js';
|
||||
import { Mp4File } from '!/@dantheman827/taglib-ts/src/mp4/mp4File.js';
|
||||
import { MpegFile } from '!/@dantheman827/taglib-ts/src/mpeg/mpegFile.js';
|
||||
import { FlacFile } from '!/@dantheman827/taglib-ts/src/flac/flacFile.js';
|
||||
import { Mp4Atom, Mp4Atoms } from '!/@dantheman827/taglib-ts/src/mp4/mp4Atoms.js';
|
||||
import { ByteVector, StringType } from '!/@dantheman827/taglib-ts/src/byteVector.js';
|
||||
import { Mp4Codec } from '!/@dantheman827/taglib-ts/src/mp4/mp4Properties.js';
|
||||
import { OggFile } from '!/@dantheman827/taglib-ts/src/ogg/oggFile.js';
|
||||
import { ffmpeg } from './ffmpeg.js';
|
||||
|
||||
vi.mock(import('./storage.js'), async (importOriginal) => {
|
||||
const mod = await importOriginal();
|
||||
|
||||
return {
|
||||
...mod,
|
||||
preferDolbyAtmosSettings: {
|
||||
...mod.preferDolbyAtmosSettings,
|
||||
isEnabled: vi.fn(),
|
||||
setEnabled: vi.fn(),
|
||||
},
|
||||
losslessContainerSettings: {
|
||||
...mod.losslessContainerSettings,
|
||||
getContainer: vi.fn(),
|
||||
setContainer: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock(import('./ffmpeg.js'), async (importOriginal) => {
|
||||
const mod = await importOriginal();
|
||||
|
||||
return {
|
||||
...mod,
|
||||
ffmpeg: vi.fn(mod.ffmpeg),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock(import('./doTimed.js'), async (importOriginal) => {
|
||||
const mod = await importOriginal();
|
||||
|
||||
return {
|
||||
...mod,
|
||||
doTimed: (label: string, fn: () => any) => {
|
||||
return fn() as any;
|
||||
},
|
||||
doTimedAsync<T, R = T extends Promise<T> ? Promise<T> : T>(
|
||||
message: string,
|
||||
callback: () => R,
|
||||
throwError: boolean = false
|
||||
): R {
|
||||
return new Promise<R>(async (resolve, reject) => {
|
||||
try {
|
||||
const ret = await callback();
|
||||
resolve(ret);
|
||||
} catch (err) {
|
||||
if (throwError) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(undefined);
|
||||
}
|
||||
}) as R;
|
||||
},
|
||||
} satisfies typeof import('./doTimed.js');
|
||||
});
|
||||
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
|
||||
enum Detection {
|
||||
DolbyAtmos,
|
||||
FlacHD,
|
||||
FlacLossless,
|
||||
AlacHD,
|
||||
AlacLossless,
|
||||
Mp4Flac,
|
||||
AacLow,
|
||||
AacReallyLow,
|
||||
AacHigh,
|
||||
AAC_256,
|
||||
MP3_320,
|
||||
MP3_256,
|
||||
MP3_128,
|
||||
OGG_320,
|
||||
OGG_256,
|
||||
OGG_128,
|
||||
}
|
||||
|
||||
suite('Track Downloads', async () => {
|
||||
const SILENCE_TRACK = 46022548;
|
||||
const TRACK_ATMOS = 463900720; // Taylor Swift - The Fate of Ophelia
|
||||
const TRACK_NO_LOSSLESS = 31097959; // deadmau5 - while(1<2)
|
||||
|
||||
const { LosslessAPI } = await import('./api.js');
|
||||
await MusicAPI.initialize(apiSettings);
|
||||
await LyricsManager.initialize(apiSettings);
|
||||
await HiFiClient.initialize();
|
||||
|
||||
const api: LosslessAPI = MusicAPI.instance.tidalAPI;
|
||||
|
||||
async function downloadTrack(trackId: number, quality: string) {
|
||||
const track = await (await HiFiClient.instance.getInfo(trackId)).json();
|
||||
return await api.downloadTrack(trackId.toString(), quality, undefined, {
|
||||
track: track.data,
|
||||
triggerDownload: false,
|
||||
});
|
||||
}
|
||||
|
||||
test.beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test.each([
|
||||
{
|
||||
display_quality: 'Dolby Atmos',
|
||||
quality: 'HI_RES_LOSSLESS',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: true,
|
||||
trackId: TRACK_ATMOS,
|
||||
detection: Detection.DolbyAtmos,
|
||||
ffmpegCalls: 0,
|
||||
},
|
||||
{
|
||||
display_quality: 'HD Lossless (FLAC)',
|
||||
quality: 'HI_RES_LOSSLESS',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.FlacHD,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
{
|
||||
display_quality: 'Lossless (FLAC)',
|
||||
quality: 'LOSSLESS',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.FlacLossless,
|
||||
ffmpegCalls: 0,
|
||||
},
|
||||
{
|
||||
display_quality: 'HD Lossless (ALAC)',
|
||||
quality: 'HI_RES_LOSSLESS',
|
||||
container: 'alac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.AlacHD,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
{
|
||||
display_quality: 'Lossless (ALAC)',
|
||||
quality: 'LOSSLESS',
|
||||
container: 'alac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.AlacLossless,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
{
|
||||
display_quality: 'HD Lossless (Unchanged)',
|
||||
quality: 'HI_RES_LOSSLESS',
|
||||
container: 'nochange',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.Mp4Flac,
|
||||
ffmpegCalls: 0,
|
||||
},
|
||||
{
|
||||
display_quality: 'Lossless (Unchanged)',
|
||||
quality: 'LOSSLESS',
|
||||
container: 'nochange',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.FlacLossless,
|
||||
ffmpegCalls: 0,
|
||||
},
|
||||
{
|
||||
display_quality: 'Lossless, but not really',
|
||||
quality: 'HI_RES_LOSSLESS',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: TRACK_NO_LOSSLESS,
|
||||
detection: Detection.AacReallyLow,
|
||||
ffmpegCalls: 0,
|
||||
},
|
||||
{
|
||||
display_quality: 'High',
|
||||
quality: 'HIGH',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.AacHigh,
|
||||
ffmpegCalls: 0,
|
||||
},
|
||||
{
|
||||
display_quality: 'Low',
|
||||
quality: 'LOW',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.AacLow,
|
||||
ffmpegCalls: 0,
|
||||
},
|
||||
|
||||
{
|
||||
display_quality: 'AAC 256',
|
||||
quality: 'FFMPEG_AAC_256',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: TRACK_ATMOS,
|
||||
detection: Detection.AAC_256,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
|
||||
{
|
||||
display_quality: 'MP3 320',
|
||||
quality: 'FFMPEG_MP3_320',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.MP3_320,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
{
|
||||
display_quality: 'MP3 256',
|
||||
quality: 'FFMPEG_MP3_256',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.MP3_256,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
{
|
||||
display_quality: 'MP3 128',
|
||||
quality: 'FFMPEG_MP3_128',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.MP3_128,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
|
||||
{
|
||||
display_quality: 'OGG 320',
|
||||
quality: 'FFMPEG_OGG_320',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.OGG_320,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
{
|
||||
display_quality: 'OGG 256',
|
||||
quality: 'FFMPEG_OGG_256',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.OGG_256,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
{
|
||||
display_quality: 'OGG 128',
|
||||
quality: 'FFMPEG_OGG_128',
|
||||
container: 'flac',
|
||||
preferDolbyAtmos: false,
|
||||
trackId: SILENCE_TRACK,
|
||||
detection: Detection.OGG_128,
|
||||
ffmpegCalls: 1,
|
||||
},
|
||||
])('$display_quality', async ({ quality, container, preferDolbyAtmos, trackId, detection, ffmpegCalls }) => {
|
||||
vi.mocked(preferDolbyAtmosSettings.isEnabled).mockReturnValue(preferDolbyAtmos);
|
||||
vi.mocked(losslessContainerSettings.getContainer).mockReturnValue(container);
|
||||
|
||||
const blob = await downloadTrack(trackId, quality);
|
||||
expect(ffmpeg).toHaveBeenCalledTimes(ffmpegCalls);
|
||||
const file = await FileRef.fromBlob(blob);
|
||||
const stream = file.file().stream();
|
||||
|
||||
expect(file.isValid).toBe(true);
|
||||
|
||||
let trak: Mp4Atom | null = null;
|
||||
let stsd: Mp4Atom | null = null;
|
||||
let stsdData: ByteVector | null = null;
|
||||
|
||||
const streamPosition = await stream.tell();
|
||||
|
||||
if (file.file() instanceof Mp4File) {
|
||||
const atoms = await Mp4Atoms.create(stream);
|
||||
const moov = atoms.find('moov');
|
||||
expect(moov).not.toBeNull();
|
||||
|
||||
let trak: Mp4Atom | null = null;
|
||||
let data: ByteVector;
|
||||
|
||||
const trakList = moov.findAll('trak');
|
||||
for (const track of trakList) {
|
||||
const hdlr = track.find('mdia', 'hdlr');
|
||||
if (!hdlr) continue;
|
||||
trak = track;
|
||||
await stream.seek(hdlr.offset);
|
||||
data = await stream.readBlock(hdlr.length);
|
||||
if (data.containsAt(ByteVector.fromString('soun', StringType.Latin1), 16)) {
|
||||
break;
|
||||
}
|
||||
trak = null;
|
||||
}
|
||||
expect(trak).toBeInstanceOf(Mp4Atom);
|
||||
stsd = trak!.find('mdia', 'minf', 'stbl', 'stsd');
|
||||
expect(stsd).toBeInstanceOf(Mp4Atom);
|
||||
await stream.seek(stsd.offset);
|
||||
stsdData = await stream.readBlock(stsd.length);
|
||||
}
|
||||
|
||||
stream.seek(streamPosition);
|
||||
|
||||
switch (detection) {
|
||||
case Detection.DolbyAtmos: {
|
||||
expect(file.file()).toBeInstanceOf(Mp4File);
|
||||
const codec = stsdData.toString().substring(20, 24);
|
||||
expect(codec).toBe('ec-3');
|
||||
break;
|
||||
}
|
||||
case Detection.FlacHD: {
|
||||
expect(file.file()).toBeInstanceOf(FlacFile);
|
||||
const flac = file.file() as FlacFile;
|
||||
expect(flac.audioProperties().bitsPerSample).toBe(24);
|
||||
expect(flac.audioProperties().sampleRate).toBe(176400);
|
||||
break;
|
||||
}
|
||||
case Detection.FlacLossless: {
|
||||
expect(file.file()).toBeInstanceOf(FlacFile);
|
||||
const flac = file.file() as FlacFile;
|
||||
expect(flac.audioProperties().bitsPerSample).toBe(16);
|
||||
expect(flac.audioProperties().sampleRate).toBe(44100);
|
||||
break;
|
||||
}
|
||||
case Detection.Mp4Flac: {
|
||||
expect(file.file()).toBeInstanceOf(Mp4File);
|
||||
const codec = stsdData.toString().substring(20, 24);
|
||||
expect(codec).toBe('fLaC');
|
||||
break;
|
||||
}
|
||||
case Detection.AlacHD: {
|
||||
expect(file.file()).toBeInstanceOf(Mp4File);
|
||||
const mp4 = file.file() as Mp4File;
|
||||
expect(mp4.audioProperties().codec).toBe(Mp4Codec.ALAC);
|
||||
expect(mp4.audioProperties().bitsPerSample).toBe(24);
|
||||
expect(mp4.audioProperties().sampleRate).toBe(176400);
|
||||
break;
|
||||
}
|
||||
case Detection.AlacLossless: {
|
||||
expect(file.file()).toBeInstanceOf(Mp4File);
|
||||
const mp4 = file.file() as Mp4File;
|
||||
expect(mp4.audioProperties().codec).toBe(Mp4Codec.ALAC);
|
||||
expect(mp4.audioProperties().bitsPerSample).toBe(16);
|
||||
expect(mp4.audioProperties().sampleRate).toBe(44100);
|
||||
break;
|
||||
}
|
||||
case Detection.AacLow: {
|
||||
expect(file.file()).toBeInstanceOf(Mp4File);
|
||||
const mp4 = file.file() as Mp4File;
|
||||
expect(mp4.audioProperties().codec).toBe(Mp4Codec.AAC);
|
||||
expect(mp4.audioProperties().bitsPerSample).toBe(16);
|
||||
expect(mp4.audioProperties().sampleRate).toBe(44100);
|
||||
expect(mp4.audioProperties().bitrate).toBe(97);
|
||||
break;
|
||||
}
|
||||
case Detection.AacReallyLow: {
|
||||
expect(file.file()).toBeInstanceOf(Mp4File);
|
||||
const mp4 = file.file() as Mp4File;
|
||||
expect(mp4.audioProperties().codec).toBe(Mp4Codec.AAC);
|
||||
expect(mp4.audioProperties().bitsPerSample).toBe(16);
|
||||
expect(mp4.audioProperties().sampleRate).toBe(22050);
|
||||
expect(mp4.audioProperties().bitrate).toBe(97);
|
||||
break;
|
||||
}
|
||||
case Detection.AacHigh: {
|
||||
expect(file.file()).toBeInstanceOf(Mp4File);
|
||||
const mp4 = file.file() as Mp4File;
|
||||
expect(mp4.audioProperties().codec).toBe(Mp4Codec.AAC);
|
||||
expect(mp4.audioProperties().bitsPerSample).toBe(16);
|
||||
expect(mp4.audioProperties().sampleRate).toBe(44100);
|
||||
expect(mp4.audioProperties().bitrate).toBe(322);
|
||||
break;
|
||||
}
|
||||
|
||||
case Detection.AAC_256: {
|
||||
expect(file.file()).toBeInstanceOf(Mp4File);
|
||||
const mp4 = file.file() as Mp4File;
|
||||
expect(mp4.audioProperties().codec).toBe(Mp4Codec.AAC);
|
||||
expect(mp4.audioProperties().bitsPerSample).toBe(16);
|
||||
expect(mp4.audioProperties().sampleRate).toBe(44100);
|
||||
expect(mp4.audioProperties().bitrate).toBe(263);
|
||||
break;
|
||||
}
|
||||
|
||||
case Detection.MP3_320: {
|
||||
expect(file.file()).toBeInstanceOf(MpegFile);
|
||||
const mp3 = file.file() as MpegFile;
|
||||
expect(mp3.audioProperties().sampleRate).toBe(44100);
|
||||
expect(mp3.audioProperties().bitrate).toBe(322);
|
||||
break;
|
||||
}
|
||||
|
||||
case Detection.MP3_256: {
|
||||
expect(file.file()).toBeInstanceOf(MpegFile);
|
||||
const mp3 = file.file() as MpegFile;
|
||||
expect(mp3.audioProperties().sampleRate).toBe(44100);
|
||||
expect(mp3.audioProperties().bitrate).toBe(258);
|
||||
break;
|
||||
}
|
||||
|
||||
case Detection.MP3_128: {
|
||||
expect(file.file()).toBeInstanceOf(MpegFile);
|
||||
const mp3 = file.file() as MpegFile;
|
||||
expect(mp3.audioProperties().sampleRate).toBe(44100);
|
||||
expect(mp3.audioProperties().bitrate).toBe(129);
|
||||
break;
|
||||
}
|
||||
|
||||
case Detection.OGG_320: {
|
||||
expect(file.file()).toBeInstanceOf(OggFile);
|
||||
const ogg = file.file() as OggFile;
|
||||
expect(ogg.audioProperties().sampleRate).toBe(44100);
|
||||
//expect(ogg.audioProperties().bitrate).toBe(320);
|
||||
break;
|
||||
}
|
||||
|
||||
case Detection.OGG_256: {
|
||||
expect(file.file()).toBeInstanceOf(OggFile);
|
||||
const ogg = file.file() as OggFile;
|
||||
expect(ogg.audioProperties().sampleRate).toBe(44100);
|
||||
//expect(ogg.audioProperties().bitrate).toBe(256);
|
||||
break;
|
||||
}
|
||||
|
||||
case Detection.OGG_128: {
|
||||
expect(file.file()).toBeInstanceOf(OggFile);
|
||||
const ogg = file.file() as OggFile;
|
||||
expect(ogg.audioProperties().sampleRate).toBe(44100);
|
||||
//expect(ogg.audioProperties().bitrate).toBe(128);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error('Unknown detection type');
|
||||
}
|
||||
});
|
||||
});
|
||||
12
js/ffmpeg.js
12
js/ffmpeg.js
|
|
@ -49,7 +49,8 @@ async function ffmpegWorker(
|
|||
onProgress = null,
|
||||
signal = null,
|
||||
extraFiles = [],
|
||||
logConsole = true
|
||||
logConsole = true,
|
||||
rawArgs = false
|
||||
) {
|
||||
const audioData = audioBlob ? await audioBlob.arrayBuffer() : null;
|
||||
const assets = loadFfmpeg();
|
||||
|
|
@ -128,7 +129,7 @@ async function ffmpegWorker(
|
|||
{
|
||||
audioData,
|
||||
extraFiles,
|
||||
args,
|
||||
...(rawArgs ? { rawArgs: args } : { args }),
|
||||
output: {
|
||||
name: outputName,
|
||||
mime: outputMime,
|
||||
|
|
@ -153,6 +154,7 @@ async function ffmpegWorker(
|
|||
* @param {AbortSignal|null} [opts.signal=null] - Optional abort signal to cancel encoding
|
||||
* @param {Array} [opts.extraFiles=[]] - Additional files to provide to FFmpeg
|
||||
* @param {Boolean} [opts.logConsole=true] - Whether to log FFmpeg output to the console
|
||||
* @param {string[]} [opts.rawArgs=[]] - Whether to pass args as raw command line (without default input/output)
|
||||
* @returns {Promise<Blob>} Encoded audio blob
|
||||
* @throws {FfmpegError} If Web Workers are not available
|
||||
* @throws {Error} If FFmpeg encoding fails
|
||||
|
|
@ -167,6 +169,7 @@ export async function ffmpeg(
|
|||
signal = null,
|
||||
extraFiles = [],
|
||||
logConsole = true,
|
||||
rawArgs = null,
|
||||
} = {}
|
||||
) {
|
||||
try {
|
||||
|
|
@ -174,13 +177,14 @@ export async function ffmpeg(
|
|||
if (typeof Worker !== 'undefined') {
|
||||
return await ffmpegWorker(
|
||||
audioBlob,
|
||||
args,
|
||||
rawArgs || args,
|
||||
outputName,
|
||||
outputMime,
|
||||
onProgress,
|
||||
signal,
|
||||
extraFiles,
|
||||
logConsole
|
||||
logConsole,
|
||||
!!rawArgs
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
19
js/ffmpeg.test.ts
Normal file
19
js/ffmpeg.test.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { expect, test, suite } from 'vitest';
|
||||
import { ffmpeg } from './ffmpeg';
|
||||
|
||||
test('Run `ffmpeg --help`', async () => {
|
||||
const lines: string[] = [];
|
||||
const info = await ffmpeg(null, {
|
||||
rawArgs: ['--help'],
|
||||
logConsole: false,
|
||||
outputName: null,
|
||||
onProgress: (progress) => {
|
||||
if (progress.stage == 'stdout') {
|
||||
lines.push(progress.message);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
expect(lines).length.greaterThan(0);
|
||||
expect(lines[0]).matches(/ffmpeg version/i);
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { FFmpeg } from '@ffmpeg/ffmpeg';
|
||||
import { FFmpeg } from '!/@ffmpeg/ffmpeg/dist/esm/classes.js';
|
||||
|
||||
let ffmpeg = null;
|
||||
let loadingPromise = null;
|
||||
|
|
@ -99,6 +99,7 @@ self.onmessage = async (e) => {
|
|||
const {
|
||||
audioData,
|
||||
extraFiles = [],
|
||||
rawArgs,
|
||||
args = [],
|
||||
output = {
|
||||
name: 'output',
|
||||
|
|
@ -123,7 +124,7 @@ self.onmessage = async (e) => {
|
|||
await ffmpeg.writeFile(file.name, new Uint8Array(file.data));
|
||||
}
|
||||
|
||||
const ffmpegArgs = ['-i', 'input', ...args, ...(output.name ? [output.name] : [])];
|
||||
const ffmpegArgs = rawArgs || ['-i', 'input', ...args, ...(output.name ? [output.name] : [])];
|
||||
self.postMessage({ type: 'command', command: ffmpegArgs });
|
||||
|
||||
const exitCode = await ffmpeg.exec(ffmpegArgs);
|
||||
|
|
|
|||
12
package.json
12
package.json
|
|
@ -5,6 +5,11 @@
|
|||
"description": "[<img src=\"https://github.com/monochrome-music/monochrome/blob/main/assets/512.png?raw=true\" alt=\"Monochrome Logo\">](https://monochrome.tf)",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"test": "vitest run --config=vite.config.ts",
|
||||
"test:headless": "HEADLESS=true vitest run --config=vite.config.ts",
|
||||
"test:watch": "vitest --config=vite.config.ts",
|
||||
"test:watch:headless": "HEADLESS=true vitest --config=vite.config.ts",
|
||||
"install:playwright": "playwright install chromium",
|
||||
"build": "vite build",
|
||||
"postbuild": "node -e \"const fs = require('fs'); const path = require('path'); const src = 'extensions'; const dest = path.join('dist', 'Monochrome', 'extensions'); if (fs.existsSync(src)) { fs.mkdirSync(dest, { recursive: true }); fs.cpSync(src, dest, { recursive: true }); console.log('Extensions manually copied to ' + dest); }\"",
|
||||
"preview": "vite preview",
|
||||
|
|
@ -28,13 +33,16 @@
|
|||
"devDependencies": {
|
||||
"@capacitor/assets": "^3.0.5",
|
||||
"@capacitor/cli": "^8.2.0",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@types/node": "^25.3.5",
|
||||
"@vitest/browser-playwright": "^4.1.2",
|
||||
"eslint": "^9.39.3",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"formidable": "^3.5.4",
|
||||
"globals": "^17.4.0",
|
||||
"htmlhint": "^1.9.2",
|
||||
"miniflare": "^4.20260301.1",
|
||||
"playwright": "^1.58.2",
|
||||
"prettier": "^3.8.1",
|
||||
"stylelint": "^16.26.1",
|
||||
"stylelint-config-standard": "^39.0.1",
|
||||
|
|
@ -61,6 +69,7 @@
|
|||
"@kawarp/core": "^1.1.1",
|
||||
"@svta/common-media-library": "^0.18.1",
|
||||
"@uimaxbai/am-lyrics": "^1.1.4",
|
||||
"@vitest/web-worker": "^4.1.2",
|
||||
"appwrite": "^23.0.0",
|
||||
"butterchurn": "^2.6.7",
|
||||
"butterchurn-presets": "^2.4.7",
|
||||
|
|
@ -79,6 +88,7 @@
|
|||
"simple-icons": "^16.12.0",
|
||||
"svgo": "^4.0.1",
|
||||
"url-toolkit": "^2.2.5",
|
||||
"uuid": "^13.0.0"
|
||||
"uuid": "^13.0.0",
|
||||
"vitest": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable", "webworker"],
|
||||
"types": ["vite/client", "node"],
|
||||
"types": ["vite/client", "node", "./js/global.d.ts"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"!/*": ["node_modules/*"]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { gzipSync, constants as zlibConstants } from 'zlib';
|
||||
import type { Plugin } from 'vite';
|
||||
import type { Plugin, ResolvedConfig } from 'vite';
|
||||
import mime from 'mime';
|
||||
import { createHash } from 'crypto';
|
||||
|
||||
|
|
@ -26,10 +26,14 @@ function hashString(input: string, algorithm = 'sha256'): string {
|
|||
*/
|
||||
export default function blobAssetPlugin(): Plugin {
|
||||
const devAssets = new Map<string, Buffer>();
|
||||
let resolvedConfig: ResolvedConfig | null = null;
|
||||
|
||||
return {
|
||||
name: 'vite-blob-asset',
|
||||
|
||||
async configResolved(config: ResolvedConfig) {
|
||||
resolvedConfig = config;
|
||||
},
|
||||
async load(id) {
|
||||
if (!id.includes('?blob-url')) return;
|
||||
|
||||
|
|
@ -45,7 +49,7 @@ export default function blobAssetPlugin(): Plugin {
|
|||
|
||||
let assetUrl: string;
|
||||
|
||||
if (this.meta.watchMode) {
|
||||
if (resolvedConfig?.command === 'serve') {
|
||||
/** dev server path */
|
||||
assetUrl = `/@blob/${hashString(absPath)}/${path.basename(filepath)}.gz`;
|
||||
devAssets.set(assetUrl, compressed);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import uploadPlugin from './vite-plugin-upload.js';
|
|||
import blobAssetPlugin from './vite-plugin-blob.js';
|
||||
import svgUse from './vite-plugin-svg-use.js';
|
||||
import { execSync } from 'child_process';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
|
||||
function getGitCommitHash() {
|
||||
try {
|
||||
|
|
@ -19,9 +20,19 @@ export default defineConfig(({ mode }) => {
|
|||
const commitHash = getGitCommitHash();
|
||||
|
||||
return {
|
||||
test: {
|
||||
// https://vitest.dev/guide/browser/
|
||||
browser: {
|
||||
enabled: true,
|
||||
provider: playwright(),
|
||||
headless: !!process.env.HEADLESS,
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
base: './',
|
||||
define: {
|
||||
__COMMIT_HASH__: JSON.stringify(commitHash),
|
||||
__VITEST__: !!process.env.VITEST,
|
||||
},
|
||||
worker: {
|
||||
format: 'es',
|
||||
|
|
|
|||
Loading…
Reference in a new issue