mirror of
https://github.com/C4illin/ConvertX.git
synced 2026-09-12 09:57:46 +02:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d34c139449 | |||
| 9d1c093780 | |||
| b9ee1f664e | |||
| 2ea8e00f1e | |||
| edb21f867b | |||
| 0dea046c20 | |||
| 878876ca17 | |||
| 57ebd7f454 | |||
| 5c96086d4e | |||
| e94d037a61 | |||
| ce8637307c | |||
| acee10f8df | |||
| f444d84d4d | |||
| 126fd4357d | |||
| 3f91d518ba | |||
| a7722c1f77 | |||
| ddd782decc | |||
| c8551d9548 | |||
| 85355c64b3 | |||
| 84e954948b | |||
| 5cf7703dd4 | |||
| 3602f12113 | |||
| 15e4046c19 | |||
| 7157d2398e | |||
| dc61643912 | |||
| 393441faa1 | |||
| cc4de7625d | |||
| 174877cd88 | |||
| eceb29c22d | |||
| 4bdb643ed4 | |||
| e125326720 | |||
| 0965928949 | |||
| 1ba82cf1b2 | |||
| 70fcc8413f | |||
| bee3f6302b | |||
| 0d201261c8 | |||
| f1439faf12 | |||
| 9cbd233dd6 | |||
| 7c93846afa | |||
| b013b4d0bc | |||
| d291f049b8 | |||
| 8aa23c75aa | |||
| 0483392f7e | |||
| debbe5d1d4 | |||
| cd32144bce | |||
| f747a5b94b | |||
| 52af8d5824 | |||
| 8129ecdc39 | |||
| 18f2aa8562 | |||
| 081f62de95 | |||
| 1032311299 | |||
| 6c812b44bb | |||
| 7a936bdc04 | |||
| 7ec6b66da8 | |||
| bb751163ba | |||
| c3f17cc5a7 | |||
| 60f6f5b77f | |||
| bbf6f6ba52 | |||
| 9802519e45 | |||
| 2b40443885 | |||
| 28e5ed4c03 | |||
| c1ca9c770a | |||
| f2a92aaf39 | |||
| 8af8e59b4f | |||
| 9ac5e7569b | |||
| 955ea5c014 | |||
| 5d70df424e | |||
| 911587ea68 | |||
| 3d84925e1b | |||
| ab352987d5 | |||
| 689e66e3f9 | |||
| 4598745956 | |||
| af199ab8bb | |||
| 20b321c308 | |||
| 8f898be20e | |||
| df3330fdc2 | |||
| 12a5580694 | |||
| eabd6e6a7f | |||
| 0521af0d52 | |||
| 826dc1062a | |||
| 84d7da0195 | |||
| ad000f35ba | |||
| 2f965035b6 | |||
| e3dc013b20 | |||
| a59a9fdeec | |||
| f5bc47b532 | |||
| bef07a787e |
@@ -31,6 +31,7 @@ RUN apt-get update && apt-get install -y \
|
||||
poppler-utils \
|
||||
potrace \
|
||||
python3-numpy \
|
||||
python3-tinycss2 \
|
||||
resvg \
|
||||
texlive \
|
||||
texlive-fonts-recommended \
|
||||
|
||||
+16
-16
@@ -2,19 +2,19 @@ changelog:
|
||||
exclude:
|
||||
authors:
|
||||
- renovate[bot]
|
||||
categories:
|
||||
- title: Features
|
||||
labels:
|
||||
- feature
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- fix
|
||||
- title: Miscellaneous Chores
|
||||
labels:
|
||||
- chore
|
||||
- ci
|
||||
- docs
|
||||
- test
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
categories:
|
||||
- title: Features
|
||||
labels:
|
||||
- feature
|
||||
- title: Bug Fixes
|
||||
labels:
|
||||
- fix
|
||||
- title: Miscellaneous Chores
|
||||
labels:
|
||||
- chore
|
||||
- ci
|
||||
- docs
|
||||
- test
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
name: autofix.ci
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: ["main"]
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Set up Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version-file: ".bun-version"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
- name: Format code
|
||||
run: |
|
||||
bun run format:eslint || true
|
||||
bun run format:prettier
|
||||
- uses: autofix-ci/action@c5b2d67aa2274e7b5a18224e8171550871fc7e4a
|
||||
@@ -1,4 +1,6 @@
|
||||
name: Check Lint
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -17,12 +19,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.2
|
||||
bun-version-file: ".bun-version"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
name: autolabel
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, edited]
|
||||
name: conventional-release-labels
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: bcoe/conventional-release-labels@v1
|
||||
- uses: c4illin/autolabel@main
|
||||
with:
|
||||
type_labels: |
|
||||
{
|
||||
"feat": "Feature",
|
||||
"fix": "Fix",
|
||||
"breaking": "Breaking",
|
||||
"chore": "Chore",
|
||||
"docs": "Docs",
|
||||
"test": "Test",
|
||||
"ci": "CI"
|
||||
}
|
||||
feat: Feature
|
||||
fix: Fix
|
||||
docs: Docs
|
||||
style: Style
|
||||
refactor: Refactor
|
||||
perf: Performance
|
||||
test: Test
|
||||
build: Build
|
||||
ci: CI
|
||||
revert: Revert
|
||||
breaking: Breaking
|
||||
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: downcase REPO
|
||||
run: |
|
||||
@@ -57,18 +57,18 @@ jobs:
|
||||
|
||||
- name: Docker meta default
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ghcr.io/${{ env.REPO }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
# here we only login to ghcr.io since the this only pushes internal images
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
|
||||
- name: Build and push by digest
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
env:
|
||||
DOCKER_BUILDKIT: 1
|
||||
with:
|
||||
@@ -96,7 +96,7 @@ jobs:
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
@@ -130,24 +130,24 @@ jobs:
|
||||
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/${{ env.REPO }}
|
||||
${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
name: Update Docker Hub Description
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
@@ -15,7 +17,7 @@ jobs:
|
||||
dockerHubDescription:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Docker Hub Description
|
||||
uses: peter-evans/dockerhub-description@v5
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
name: Check Tests
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -17,12 +19,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Set up Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.2.2
|
||||
bun-version-file: ".bun-version"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
|
||||
+12
-2
@@ -1,9 +1,10 @@
|
||||
FROM debian:trixie-slim AS base
|
||||
FROM debian:testing-slim AS base
|
||||
LABEL org.opencontainers.image.source="https://github.com/C4illin/ConvertX"
|
||||
WORKDIR /app
|
||||
|
||||
# install bun
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
@@ -61,6 +62,7 @@ RUN apt-get update && apt-get install -y \
|
||||
libreoffice \
|
||||
libva2 \
|
||||
libvips-tools \
|
||||
libemail-address-perl \
|
||||
libemail-outlook-message-perl \
|
||||
lmodern \
|
||||
mupdf-tools \
|
||||
@@ -68,15 +70,23 @@ RUN apt-get update && apt-get install -y \
|
||||
poppler-utils \
|
||||
potrace \
|
||||
python3-numpy \
|
||||
python3-tinycss2 \
|
||||
resvg \
|
||||
texlive \
|
||||
texlive-fonts-recommended \
|
||||
texlive-latex-extra \
|
||||
texlive-latex-recommended \
|
||||
texlive-xetex \
|
||||
python3 \
|
||||
python3-pip \
|
||||
pipx \
|
||||
--no-install-recommends \
|
||||
&& pipx install "markitdown[all]" \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Add pipx bin directory to PATH
|
||||
ENV PATH="/root/.local/bin:${PATH}"
|
||||
|
||||
# Install VTracer binary
|
||||
RUN ARCH=$(uname -m) && \
|
||||
if [ "$ARCH" = "aarch64" ]; then \
|
||||
@@ -101,4 +111,4 @@ EXPOSE 3000/tcp
|
||||
# used for calibre
|
||||
ENV QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox"
|
||||
ENV NODE_ENV=production
|
||||
ENTRYPOINT [ "bun", "run", "dist/src/index.js" ]
|
||||
ENTRYPOINT [ "bun", "run", "dist/src/index.js" ]
|
||||
|
||||
@@ -38,6 +38,7 @@ A self-hosted online file converter. Supports over a thousand different formats.
|
||||
| [Dasel](https://github.com/TomWright/dasel) | Data Files | 5 | 4 |
|
||||
| [Pandoc](https://pandoc.org/) | Documents | 43 | 65 |
|
||||
| [msgconvert](https://github.com/mvz/email-outlook-message-perl) | Outlook | 1 | 1 |
|
||||
| VCF to CSV | Contacts | 1 | 1 |
|
||||
| [dvisvgm](https://dvisvgm.de/) | Vector images | 4 | 2 |
|
||||
| [ImageMagick](https://imagemagick.org/) | Images | 245 | 183 |
|
||||
| [GraphicsMagick](http://www.graphicsmagick.org/) | Images | 167 | 130 |
|
||||
@@ -45,6 +46,8 @@ A self-hosted online file converter. Supports over a thousand different formats.
|
||||
| [FFmpeg](https://ffmpeg.org/) | Video | ~472 | ~199 |
|
||||
| [Potrace](https://potrace.sourceforge.net/) | Raster to vector | 4 | 11 |
|
||||
| [VTracer](https://github.com/visioncortex/vtracer) | Raster to vector | 8 | 1 |
|
||||
| [Markitdown](https://github.com/microsoft/markitdown) | Documents | 6 | 1 |
|
||||
| [pdftops](https://poppler.freedesktop.org/) | Documents | 1 | 2 |
|
||||
|
||||
<!-- many ffmpeg fileformats are duplicates -->
|
||||
|
||||
@@ -85,19 +88,21 @@ If you get unable to open database file run `chown -R $USER:$USER path` on the p
|
||||
|
||||
All are optional, JWT_SECRET is recommended to be set.
|
||||
|
||||
| Name | Default | Description |
|
||||
| ---------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||||
| JWT_SECRET | when unset it will use the value from randomUUID() | A long and secret string used to sign the JSON Web Token |
|
||||
| ACCOUNT_REGISTRATION | false | Allow users to register accounts |
|
||||
| HTTP_ALLOWED | false | Allow HTTP connections, only set this to true locally |
|
||||
| ALLOW_UNAUTHENTICATED | false | Allow unauthenticated users to use the service, only set this to true locally |
|
||||
| AUTO_DELETE_EVERY_N_HOURS | 24 | Checks every n hours for files older then n hours and deletes them, set to 0 to disable |
|
||||
| WEBROOT | | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" |
|
||||
| FFMPEG_ARGS | | Arguments to pass to ffmpeg, e.g. `-preset veryfast` |
|
||||
| HIDE_HISTORY | false | Hide the history page |
|
||||
| LANGUAGE | en | Language to format date strings in, specified as a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) |
|
||||
| UNAUTHENTICATED_USER_SHARING | false | Shares conversion history between all unauthenticated users |
|
||||
| MAX_CONVERT_PROCESS | 0 | Maximum number of concurrent conversion processes allowed. Set to 0 for unlimited. |
|
||||
| Name | Default | Description |
|
||||
| ---------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| JWT_SECRET | when unset it will use the value from randomUUID() | A long and secret string used to sign the JSON Web Token |
|
||||
| ACCOUNT_REGISTRATION | false | Allow users to register accounts |
|
||||
| HTTP_ALLOWED | false | Allow HTTP connections, only set this to true locally |
|
||||
| ALLOW_UNAUTHENTICATED | false | Allow unauthenticated users to use the service, only set this to true locally |
|
||||
| AUTO_DELETE_EVERY_N_HOURS | 24 | Checks every n hours for files older then n hours and deletes them, set to 0 to disable |
|
||||
| WEBROOT | | The address to the root path setting this to "/convert" will serve the website on "example.com/convert/" |
|
||||
| FFMPEG_ARGS | | Arguments to pass to the input file of ffmpeg, e.g. `-hwaccel vaapi`. See https://github.com/C4illin/ConvertX/issues/190 for more info about hw-acceleration. |
|
||||
| FFMPEG_OUTPUT_ARGS | | Arguments to pass to the output of ffmpeg, e.g. `-preset veryfast` |
|
||||
| HIDE_HISTORY | false | Hide the history page |
|
||||
| LANGUAGE | en | Language to format date strings in, specified as a [BCP 47 language tag](https://en.wikipedia.org/wiki/IETF_language_tag) |
|
||||
| UNAUTHENTICATED_USER_SHARING | false | Shares conversion history between all unauthenticated users |
|
||||
| MAX_CONVERT_PROCESS | 0 | Maximum number of concurrent conversion processes allowed. Set to 0 for unlimited. |
|
||||
| PORT | 3000 | Application listen port |
|
||||
|
||||
### Docker images
|
||||
|
||||
@@ -126,6 +131,8 @@ Tutorial in french: <https://belginux.com/installer-convertx-avec-docker/>
|
||||
|
||||
Tutorial in chinese: <https://xzllll.com/24092901/>
|
||||
|
||||
Tutorial in polish: <https://www.kreatywnyprogramista.pl/convertx-lokalny-konwerter-plikow>
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "convertx-frontend",
|
||||
"dependencies": {
|
||||
"@elysiajs/html": "^1.4.0",
|
||||
"@elysiajs/jwt": "^1.4.0",
|
||||
"@elysiajs/static": "^1.4.6",
|
||||
"@kitajs/html": "^4.2.11",
|
||||
"elysia": "^1.4.16",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"tar": "^7.5.2",
|
||||
"@elysiajs/html": "^1.4.2",
|
||||
"@elysiajs/jwt": "^1.4.2",
|
||||
"@elysiajs/static": "^1.4.10",
|
||||
"@kitajs/html": "^4.2.13",
|
||||
"elysia": "1.4.22",
|
||||
"sanitize-filename": "^1.6.4",
|
||||
"tar": "^7.5.22",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@kitajs/ts-html-plugin": "^4.1.3",
|
||||
"@tailwindcss/cli": "^4.1.17",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@kitajs/ts-html-plugin": "^4.1.4",
|
||||
"@tailwindcss/cli": "^4.3.3",
|
||||
"@tailwindcss/postcss": "^4.3.3",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^24.10.1",
|
||||
"@typescript-eslint/parser": "^8.46.4",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-better-tailwindcss": "^3.7.10",
|
||||
"globals": "^16.5.0",
|
||||
"knip": "^5.69.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"@types/node": "^24.13.3",
|
||||
"eslint": "^10.8.1",
|
||||
"eslint-plugin-better-tailwindcss": "^4.7.0",
|
||||
"globals": "^17.9.0",
|
||||
"knip": "^6.32.1",
|
||||
"npm-run-all2": "^9.0.3",
|
||||
"postcss": "^8.5.26",
|
||||
"prettier": "^3.9.6",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.4",
|
||||
"typescript-eslint": "^8.67.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -43,39 +41,37 @@
|
||||
"packages": {
|
||||
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
|
||||
|
||||
"@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="],
|
||||
"@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="],
|
||||
|
||||
"@elysiajs/html": ["@elysiajs/html@1.4.0", "", { "dependencies": { "@kitajs/html": "^4.1.0", "@kitajs/ts-html-plugin": "^4.0.1" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-j4jFqGEkIC8Rg2XiTOujb9s0WLnz1dnY/4uqczyCdOVruDeJtGP+6+GvF0A76SxEvltn8UR1yCUnRdLqRi3vuw=="],
|
||||
"@elysiajs/html": ["@elysiajs/html@1.4.2", "", { "dependencies": { "@kitajs/html": "^4.1.0", "@kitajs/ts-html-plugin": "^4.0.1" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Db7dmbkN7gptckMpU0/Fq9Qi3QuhQr/CH60A+8rs+RT+74NUC8sONs5nkfMm5oL+6kCUWCv19uUwOjBP7zsjYQ=="],
|
||||
|
||||
"@elysiajs/jwt": ["@elysiajs/jwt@1.4.0", "", { "dependencies": { "jose": "^6.0.11" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Z0PvZhQxdDeKZ8HslXzDoXXD83NKExNPmoiAPki3nI2Xvh5wtUrBH+zWOD17yP14IbRo8fxGj3L25MRCAPsgPA=="],
|
||||
"@elysiajs/jwt": ["@elysiajs/jwt@1.4.2", "", { "dependencies": { "jose": "^6.0.11" }, "peerDependencies": { "elysia": ">= 1.4.27" } }, "sha512-gSPjF8KoL7cQIxL3kb+Ui9g5jpBdO4iUTe2+F52w66JVFct2K8cTLum8PEArxRZNg4PBrg100h6i1bJ2zfcqMg=="],
|
||||
|
||||
"@elysiajs/static": ["@elysiajs/static@1.4.7", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Go4kIXZ0G3iWfkAld07HmLglqIDMVXdyRKBQK/sVEjtpDdjHNb+rUIje73aDTWpZYg4PEVHUpi9v4AlNEwrQug=="],
|
||||
"@elysiajs/static": ["@elysiajs/static@1.4.10", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-BN/3PZVWjqkL/6TM7RH69Pae89CfU0OqfEYPeBp2hB/GR5Euds39Wy+0ug9Vbvu++GG4tRRANf2QjaLnriJveg=="],
|
||||
|
||||
"@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||
"@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
|
||||
"@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.7.0", "", { "dependencies": { "@eslint/core": "^1.2.1" } }, "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
"@eslint/core": ["@eslint/core@1.2.1", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ=="],
|
||||
|
||||
"@eslint/css-tree": ["@eslint/css-tree@3.6.8", "", { "dependencies": { "mdn-data": "2.23.0", "source-map-js": "^1.0.1" } }, "sha512-s0f40zY7dlMp8i0Jf0u6l/aSswS0WRAgkhgETgiCJRcxIWb4S/Sp9uScKHWbkM3BnoFLbJbmOYk5AZUDFVxaLA=="],
|
||||
"@eslint/css-tree": ["@eslint/css-tree@4.0.4", "", { "dependencies": { "mdn-data": "2.28.1", "source-map-js": "^1.2.1" } }, "sha512-nxMparyhqVWQvadx9x8dIfubfIPOE+X2b2waua8fzdnM9vdp9rgVtwEZlG0TmCwEUz/d/f40fzvO/eqBwdxz0A=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.3", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ=="],
|
||||
"@eslint/js": ["@eslint/js@10.0.1", "", { "peerDependencies": { "eslint": "^10.0.0" }, "optionalPeers": ["eslint"] }, "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
|
||||
"@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.7.2", "", { "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" } }, "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
|
||||
|
||||
@@ -97,55 +93,91 @@
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@kitajs/html": ["@kitajs/html@4.2.11", "", { "dependencies": { "csstype": "^3.1.3" } }, "sha512-gOe+zzCZKN2fPT1FUK32mHsr21ILcAOUUux/yDqQthInW8egN8RuxVp+zP3KhwWETVACkurBiKV9RWuNw+ceiw=="],
|
||||
"@kitajs/html": ["@kitajs/html@4.2.13", "", { "dependencies": { "csstype": "^3.1.3" } }, "sha512-o+8e61EsoLDPTP7rsPkYolca1YFybHuxU2Lr5fWDZCUkYT/6uBlVkvnZUdCXMQKentJL9dxwpR8/xK2Q+U4LhA=="],
|
||||
|
||||
"@kitajs/ts-html-plugin": ["@kitajs/ts-html-plugin@4.1.3", "", { "dependencies": { "chalk": "^5.6.2", "tslib": "^2.8.1", "yargs": "^18.0.0" }, "peerDependencies": { "@kitajs/html": "^4.2.10", "typescript": "^5.6.2" }, "bin": { "ts-html-plugin": "dist/cli.js", "xss-scan": "dist/cli.js" } }, "sha512-NlYrID5yMxfRKiO1eiiSC4MWveKe0ffoCJOZm4idNOqwimmLXr0g1NmvCcquOU2XLRrgzynxZqw6rhwR5CY5Nw=="],
|
||||
"@kitajs/ts-html-plugin": ["@kitajs/ts-html-plugin@4.1.4", "", { "dependencies": { "chalk": "^5.6.2", "tslib": "^2.8.1", "yargs": "^18.0.0" }, "peerDependencies": { "@kitajs/html": "^4.2.10", "typescript": "^5.9.3" }, "bin": { "ts-html-plugin": "dist/cli.js", "xss-scan": "dist/cli.js" } }, "sha512-xK5mNrhnIy73kJFKx5yVGChJyWFRGmIaE0sjlVxVYllk5dyaEYVCrIh1N8AfnseEHka8gAqzPGW95HlkhDvnJA=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw=="],
|
||||
|
||||
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
|
||||
"@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.142.0", "", { "os": "android", "cpu": "arm" }, "sha512-ZiRGDutGsv1G6bL/ozy/koC0Sv39T1DqyoC4KD1DOy9ZoACm1O5UWhEK2c02Qdk+4lfLVkvFa/mQ0fm/4h1BtQ=="],
|
||||
|
||||
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
|
||||
"@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.142.0", "", { "os": "android", "cpu": "arm64" }, "sha512-WZkvGRLNQTz8lR9zP5nLjUdlroRCopBu3g9zF1p/laE6DzT1UbQo8Rdz5MWhaJUPYg/6gp+jo7HUgsyKaN1FtQ=="],
|
||||
|
||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||
"@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.142.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-l4khS8LQOOVYsGRVARo1gSaCT/aBSceUVXgtovWc2+drnxVuDr082WA3OCHVdVzIz5JIrP/y9CWsSKxBDNmYGg=="],
|
||||
|
||||
"@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.14.0", "", { "os": "android", "cpu": "arm" }, "sha512-jB47iZ/thvhE+USCLv+XY3IknBbkKr/p7OBsQDTHode/GPw+OHRlit3NQ1bjt1Mj8V2CS7iHdSDYobZ1/0gagQ=="],
|
||||
"@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.142.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-QBsNF3nqlXmcH2B1YOPqQYmCJoy4HuIjUxGbBO/k5JAJUl68ghU2psRY2zPk+RyBaWqKP/qfL4oaFgEMCdwskA=="],
|
||||
|
||||
"@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.14.0", "", { "os": "android", "cpu": "arm64" }, "sha512-XFJ9t7d/Cz+dWLyqtTy3Xrekz+qqN4hmOU2iOUgr7u71OQsPUHIIeS9/wKanEK0l413gPwapIkyc5x9ltlOtyw=="],
|
||||
"@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.142.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-b7Q7m4Cqc6XqNhri3R+QhU+GVy646Pn+bkdhrDdWym/Fdi0ZUa+d73H9dm5H91JtbtAQ/z1d8XKMW3oOV8a4tQ=="],
|
||||
|
||||
"@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.14.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gwehBS9smA1mzK8frDsmUCHz+6baJVwkKF6qViHhoqA3kRKvIZ3k6WNP4JmF19JhOiGxRcoPa8gZRfzNgXwP2A=="],
|
||||
"@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.142.0", "", { "os": "linux", "cpu": "arm" }, "sha512-3riVS5IhdH3uCZj1Y9ftDQlR0dvLsIlw/edrRqk8JhgNd5K0XSs+UBtgh50N13CAlW9/TXj6sVGXaKNBocd0Yg=="],
|
||||
|
||||
"@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.14.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-5wwJvfuoahKiAqqAsMLOI28rqdh3P2K7HkjIWUXNMWAZq6ErX0L5rwJzu6T32+Zxw3k18C7R9IS4wDq/3Ar+6w=="],
|
||||
"@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.142.0", "", { "os": "linux", "cpu": "arm" }, "sha512-NmXUOpgpTSkhl795TiXmWppTwmSJ92RC1qvD6e4XOF+slgmo3e6Ah+kEu+6AN8s7NAOEwqGmir58MgSQSWmBSA=="],
|
||||
|
||||
"@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.14.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-MWTt+LOQNcQ6fa+Uu5VikkihLi1PSIrQqqp0QD44k2AORasNWl0jRGBTcMSBIgNe82qEQWYvlGzvOEEOBp01Og=="],
|
||||
"@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.142.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-gc0EXsKtXgerujmU2Bql3u1L1HsSQ2774R83idq/FoNMPVV/RY/1ErFsvnit7KoiP/sLvzQixeUo4Ut0ic0wmw=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.14.0", "", { "os": "linux", "cpu": "arm" }, "sha512-b6/IBqYrS3o0XiLVBsnex/wK8pTTK+hbGfAMOHVU6p7DBpwPPLgC/tav4IXoOIUCssTFz7aWh/xtUok0swn8VQ=="],
|
||||
"@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.142.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-F2XvmWSE0uWpie+jHKKIFgdVOe9ypGhkEZxKx5DuW215K6cbAC274yYaPkcM7EqY4Df3Weyhpcz3lsURyH2LVg=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.14.0", "", { "os": "linux", "cpu": "arm" }, "sha512-o2Qh5+y5YoqVK6YfzkalHdpmQ5bkbGGxuLg1pZLQ1Ift0x+Vix7DaFEpdCl5Z9xvYXogd/TwOlL0TPl4+MTFLA=="],
|
||||
"@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.142.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-wLMbT21U/QxknQsk+VvNF0b9D2/aGWhcaQQQ+VYlE8FwD5+GoWZIPPXNzyHmkYyhm0KB3itL+TBavjMatqNnYA=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.14.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-lk8mCSg0Tg4sEG73RiPjb7keGcEPwqQnBHX3Z+BR2SWe+qNHpoHcyFMNafzSvEC18vlxC04AUSoa6kJl/C5zig=="],
|
||||
"@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.142.0", "", { "os": "linux", "cpu": "none" }, "sha512-+G8F/4ckwT7FCJV4H2bt09xEzJbjNCfuL4Sp1AYNaFtFMVtgIGMuJlteT82U+K0UIZ/DzAR/LDlMFnEuajG7Kw=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.14.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-KykeIVhCM7pn93ABa0fNe8vk4XvnbfZMELne2s6P9tdJH9KMBsCFBi7a2BmSdUtTqWCAJokAcm46lpczU52Xaw=="],
|
||||
"@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.142.0", "", { "os": "linux", "cpu": "none" }, "sha512-hTsHtTLxMAfCo+rpF5K3qZJKW2NpPN/CHd4mYB3y7XlSdspHkd2gehDIofP64AacA9nWQw2tY3O7wR6UY8IVOA=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.14.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-QqPPWAcZU/jHAuam4f3zV8OdEkYRPD2XR0peVet3hoMMgsihR3Lhe7J/bLclmod297FG0+OgBYQVMh2nTN6oWA=="],
|
||||
"@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.142.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-6y7qYY3TCUDYjqswImdTGl92y+KA/80twALegQPN27kfY+bG7Ib1+L3jbmrCZQx6wrVnai9IPsEZp07I0hx7JQ=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.14.0", "", { "os": "linux", "cpu": "none" }, "sha512-DunWA+wafeG3hj1NADUD3c+DRvmyVNqF5LSHVUWA2bzswqmuEZXl3VYBSzxfD0j+UnRTFYLxf27AMptoMsepYg=="],
|
||||
"@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.142.0", "", { "os": "linux", "cpu": "x64" }, "sha512-i69kAWU+2LgoH5bR+zWiiu+UzAw7Oxkwv7COeJTeY19pn4e70nKQcr9Pm6cL2Z0Z54d+gl9qADlK/0yyuCPiBA=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.14.0", "", { "os": "linux", "cpu": "none" }, "sha512-4SRvwKTTk2k67EQr9Ny4NGf/BhlwggCI1CXwBbA9IV4oP38DH8b+NAPxDY0ySGRsWbPkG92FYOqM4AWzG4GSgA=="],
|
||||
"@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.142.0", "", { "os": "linux", "cpu": "x64" }, "sha512-4SQs678MmjYVrmhAgCWD4o0vpaFszXw9xLX5p2Z9MMFcltxiLkA88wQjh80YHjPrXtpyZ2CWI5m+1yNKM0m2Pw=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.14.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-hZKvkbsurj4JOom//R1Ab2MlC4cGeVm5zzMt4IsS3XySQeYjyMJ5TDZ3J5rQ8bVj3xi4FpJU2yFZ72GApsHQ6A=="],
|
||||
"@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.142.0", "", { "os": "none", "cpu": "arm64" }, "sha512-YHpx9N7Ln3a++Tc8rv+H7mrK1zyJQOAwCFg8LZ3lTs1T5afGWeZrLPhPT9HLnIwSjCyJqPWVMIrMxbjcmBr2oQ=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.14.0", "", { "os": "linux", "cpu": "x64" }, "sha512-hABxQXFXJurivw+0amFdeEcK67cF1BGBIN1+sSHzq3TRv4RoG8n5q2JE04Le2n2Kpt6xg4Y5+lcv+rb2mCJLgQ=="],
|
||||
"@oxc-parser/binding-wasm32-wasi": ["@oxc-parser/binding-wasm32-wasi@0.142.0", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-3pLDyY3+oogW73RM5uehNgAiR/Xfb7fvO2Q1Z1gIqZ2+50XDVQmBVlRkHXZTU4gKnQHpwETNsYQVsJ3joVB2iA=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.14.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Ln73wUB5migZRvC7obAAdqVwvFvk7AUs2JLt4g9QHr8FnqivlsjpUC9Nf2ssrybdjyQzEMjttUxPZz6aKPSAHw=="],
|
||||
"@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.142.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Had/VeVY28Oyb0K+Q4FV8KCzoBycIh93oDK6pCbya9lkzdq+ikMHMgBubsdqqlybjJmQRawCQRrnBRHyQwYvcQ=="],
|
||||
|
||||
"@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.14.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.7" }, "cpu": "none" }, "sha512-z+NbELmCOKNtWOqEB5qDfHXOSWB3kGQIIehq6nHtZwHLzdVO2oBq6De/ayhY3ygriC1XhgaIzzniY7jgrNl4Kw=="],
|
||||
"@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.142.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-GGi3+YphVHavvgs6gum2UXoNCqzHAmPt/nXkn8ZQZstV2Q1qZD1Mn8fz/nWrDkefHQtrG/+1/XrbMxsBTo6Svw=="],
|
||||
|
||||
"@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.14.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Ft0+qd7HSO61qCTLJ4LCdBGZkpKyDj1rG0OVSZL1DxWQoh97m7vEHd7zAvUtw8EcWjOMBQuX4mfRap/x2MOCpQ=="],
|
||||
"@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.142.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Ny/Wv4Us1LGC/ljwNTp+Hx3r/pH15EFfeDF0p+n898gt+TtRd6C9SccHcuUhDiNTb8s5tt7jdeAMDRQZ4Vq6hg=="],
|
||||
|
||||
"@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.14.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-o54jYNSfGdPxHSvXEhZg8FOV3K99mJ1f7hb1alRFb+Yec1GQXNrJXxZPIxNMYeFT13kwAWB7zuQ0HZLnDHFxfw=="],
|
||||
"@oxc-project/types": ["@oxc-project/types@0.142.0", "", {}, "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ=="],
|
||||
|
||||
"@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.14.0", "", { "os": "win32", "cpu": "x64" }, "sha512-j97icaORyM6A7GjgmUzfn7V+KGzVvctRA+eAlJb0c2OQNaETFxl6BXZdnGBDb+6oA0Y4Sr/wnekd1kQ0aVyKGg=="],
|
||||
"@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA=="],
|
||||
|
||||
"@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg=="],
|
||||
|
||||
"@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w=="],
|
||||
|
||||
"@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q=="],
|
||||
|
||||
"@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg=="],
|
||||
|
||||
"@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw=="],
|
||||
|
||||
"@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg=="],
|
||||
|
||||
"@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.24.2", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q=="],
|
||||
|
||||
"@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg=="],
|
||||
|
||||
"@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw=="],
|
||||
|
||||
"@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
|
||||
|
||||
@@ -175,99 +207,99 @@
|
||||
|
||||
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
|
||||
|
||||
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
|
||||
"@pkgr/core": ["@pkgr/core@0.3.6", "", {}, "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="],
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="],
|
||||
|
||||
"@tailwindcss/cli": ["@tailwindcss/cli@4.1.17", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "enhanced-resolve": "^5.18.3", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.1.17" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-jUIxcyUNlCC2aNPnyPEWU/L2/ik3pB4fF3auKGXr8AvN3T3OFESVctFKOBoPZQaZJIeUpPn1uCLp0MRxuek8gg=="],
|
||||
"@tailwindcss/cli": ["@tailwindcss/cli@4.3.3", "", { "dependencies": { "@parcel/watcher": "2.5.1", "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "enhanced-resolve": "^5.24.1", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.3.3" }, "bin": { "tailwindcss": "./dist/index.mjs" } }, "sha512-ZvS/n1ZHOBKcVlhkt8l5NNr1EDXk1NboYO5CYDOs6NUmvT9z6bzkwsosaJftY57T/3gWNzWMJzIXLodZC8ssdw=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="],
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="],
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.3", "", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="],
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="],
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="],
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="],
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="],
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="],
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="],
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="],
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.3", "", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="],
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="],
|
||||
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", "tailwindcss": "4.1.17" } }, "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw=="],
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.3", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "postcss": "^8.5.16", "tailwindcss": "4.3.3" } }, "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg=="],
|
||||
|
||||
"@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
|
||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||
|
||||
"@types/esrecurse": ["@types/esrecurse@4.3.1", "", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
|
||||
"@types/node": ["@types/node@24.13.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q=="],
|
||||
|
||||
"@types/prismjs": ["@types/prismjs@1.26.5", "", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="],
|
||||
"@types/prismjs": ["@types/prismjs@1.26.6", "", {}, "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/type-utils": "8.48.0", "@typescript-eslint/utils": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ=="],
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.67.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/type-utils": "8.67.0", "@typescript-eslint/utils": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.67.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ=="],
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.67.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.0", "@typescript-eslint/types": "^8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw=="],
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.67.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.67.0", "@typescript-eslint/types": "^8.67.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0" } }, "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ=="],
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0" } }, "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w=="],
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.67.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw=="],
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/utils": "8.67.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.48.0", "", {}, "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA=="],
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.67.0", "", {}, "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.0", "@typescript-eslint/tsconfig-utils": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ=="],
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.67.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.67.0", "@typescript-eslint/tsconfig-utils": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ=="],
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.67.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg=="],
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.67.0", "", { "dependencies": { "@typescript-eslint/types": "8.67.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
"@valibot/to-json-schema": ["@valibot/to-json-schema@1.7.1", "", { "peerDependencies": { "valibot": "^1.4.0" } }, "sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
|
||||
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
"ansi-styles": ["ansi-styles@7.0.0", "", {}, "sha512-kKvt3m4uwzqL0wlkPd09CmljPJGOZZ4D0fP65sqFSvPkMRKhNi+74MgIJ5QxE6SxqB4t4KyUFGg8+n5zjo6hew=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
|
||||
"brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||
|
||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||
|
||||
@@ -281,8 +313,6 @@
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
@@ -295,27 +325,27 @@
|
||||
|
||||
"detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
|
||||
|
||||
"elysia": ["elysia@1.4.16", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.3", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-KZtKN160/bdWVKg2hEgyoNXY8jRRquc+m6PboyisaLZL891I+Ufb7Ja6lDAD7vMQur8sLEWIcidZOzj5lWw9UA=="],
|
||||
"elysia": ["elysia@1.4.22", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.6", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Q90VCb1RVFxnFaRV0FDoSylESQQLWgLHFmWciQJdX9h3b2cSasji9KWEUvaJuy/L9ciAGg4RAhUVfsXHg5K2RQ=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
|
||||
"enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
|
||||
"eslint": ["eslint@10.8.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ=="],
|
||||
|
||||
"eslint-plugin-better-tailwindcss": ["eslint-plugin-better-tailwindcss@3.7.11", "", { "dependencies": { "@eslint/css-tree": "^3.6.5", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "postcss": "^8.5.6", "postcss-import": "^16.1.1", "synckit": "^0.11.11", "tailwind-csstree": "^0.1.4", "tsconfig-paths-webpack-plugin": "^4.2.0" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", "tailwindcss": "^3.3.0 || ^4.1.6" } }, "sha512-A2x5UrBoYLfZLMGvXBq/7epVMKNMxCkDRgRO1w/N1yQbFYM0L1yk6qDZV1l6Otrl0hNuBHSKFG3Uw5TDoSGStA=="],
|
||||
"eslint-plugin-better-tailwindcss": ["eslint-plugin-better-tailwindcss@4.7.0", "", { "dependencies": { "@eslint/css-tree": "^4.0.4", "@valibot/to-json-schema": "^1.7.1", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "synckit": "^0.11.13", "tailwind-csstree": "^0.3.3", "tsconfig-paths-webpack-plugin": "^4.2.0", "valibot": "^1.4.2" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", "oxlint": "^1.35.0", "tailwindcss": "^3.3.0 || ^4.1.17" }, "optionalPeers": ["eslint", "oxlint"] }, "sha512-lrdlVW4pzLPj/zX5HRqMhKPesGijDfRhnSRJMlNcsrCyJHsndmHCLKTiRNa1eREUZ6G3D3QjdLc+G4tlfGrmkw=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
"eslint-scope": ["eslint-scope@9.1.2", "", { "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
"espree": ["espree@11.2.0", "", { "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^5.0.1" } }, "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw=="],
|
||||
|
||||
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
|
||||
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
@@ -323,27 +353,23 @@
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"exact-mirror": ["exact-mirror@0.2.3", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-aLdARfO0W0ntufjDyytUJQMbNXoB9g+BbA8KcgIq4XOOTYRw48yUGON/Pr64iDrYNZKcKvKbqE0MPW56FF2BXA=="],
|
||||
"exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="],
|
||||
|
||||
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
|
||||
"fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"file-type": ["file-type@21.1.1", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg=="],
|
||||
"file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="],
|
||||
|
||||
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
|
||||
|
||||
@@ -355,51 +381,41 @@
|
||||
|
||||
"formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
|
||||
"get-east-asian-width": ["get-east-asian-width@1.5.0", "", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
|
||||
|
||||
"get-tsconfig": ["get-tsconfig@4.14.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-Dz/6HxkrxgNehhxLVeyv8sad9UzF2xBVeaKBQNDfJ5XiSXmp2gTR0eO0RWiT2NCKS5aGP9jjkOMggTN90qU50A=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
|
||||
"globals": ["globals@17.9.0", "", {}, "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
|
||||
|
||||
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
|
||||
"isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="],
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
||||
|
||||
"jose": ["jose@6.1.2", "", {}, "sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
"jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@4.0.0", "", {}, "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA=="],
|
||||
"json-parse-even-better-errors": ["json-parse-even-better-errors@6.0.0", "", {}, "sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
@@ -409,55 +425,51 @@
|
||||
|
||||
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"knip": ["knip@5.70.2", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.1", "minimist": "^1.2.8", "oxc-resolver": "^11.13.2", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.5.2", "strip-json-comments": "5.0.3", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-LI7DbeVnk7h9+FAet5KzzHNdDwJyqDa2+cn4uQfZYTfpuVjEqtGmYD9r5b9JEuOs4eVkf/7sskNhWXxELm3C/Q=="],
|
||||
"knip": ["knip@6.32.1", "", { "dependencies": { "fdir": "^6.5.0", "formatly": "^0.3.0", "get-tsconfig": "4.14.1", "jiti": "^2.7.0", "oxc-parser": "^0.142.0", "oxc-resolver": "11.24.2", "picomatch": "^4.0.5", "smol-toml": "^1.7.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.17", "unbash": "^4.0.4", "yaml": "^2.9.0", "zod": "^4.4.3" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-mIiIHMTJVUgSlz0mxEgPt7wg8DmfbCp1Txqab3WpbMCJF7YHvHtC9jeAHHXfISMl72N8WzhyG71SQlaqCOGZtg=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"mdn-data": ["mdn-data@2.23.0", "", {}, "sha512-786vq1+4079JSeu2XdcDjrhi/Ry7BWtjDl9WtGPWLiIHb2T66GvIVflZTBoSNZ5JqTtJGYEVMuFA/lbQlMOyDQ=="],
|
||||
"mdn-data": ["mdn-data@2.28.1", "", {}, "sha512-U9w+PzSZ00Z5m9rZ5ARVFL5xOfuCHdKYi/1RRwDCJsboFgJDNT3zT6PIPD7mZQYaQLhsZM3GfDRgSMRHhSmVng=="],
|
||||
|
||||
"memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
|
||||
|
||||
"memorystream": ["memorystream@0.3.1", "", {}, "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw=="],
|
||||
|
||||
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
|
||||
|
||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||
"minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
|
||||
|
||||
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
|
||||
|
||||
@@ -465,89 +477,71 @@
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
|
||||
|
||||
"npm-normalize-package-bin": ["npm-normalize-package-bin@4.0.0", "", {}, "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w=="],
|
||||
"npm-normalize-package-bin": ["npm-normalize-package-bin@6.0.0", "", {}, "sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw=="],
|
||||
|
||||
"npm-run-all2": ["npm-run-all2@8.0.4", "", { "dependencies": { "ansi-styles": "^6.2.1", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", "picomatch": "^4.0.2", "pidtree": "^0.6.0", "read-package-json-fast": "^4.0.0", "shell-quote": "^1.7.3", "which": "^5.0.0" }, "bin": { "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js", "npm-run-all": "bin/npm-run-all/index.js", "npm-run-all2": "bin/npm-run-all/index.js" } }, "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA=="],
|
||||
"npm-run-all2": ["npm-run-all2@9.0.3", "", { "dependencies": { "ansi-styles": "^7.0.0", "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", "picomatch": "^4.0.2", "pidtree": "^1.0.0", "read-package-json-fast": "^6.0.0", "shell-quote": "^1.8.4", "which": "^7.0.0" }, "bin": { "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js", "npm-run-all": "bin/npm-run-all/index.js", "npm-run-all2": "bin/npm-run-all/index.js" } }, "sha512-BQAEdU1PtYc48qYRdghW2BVTQT3VqWCoFQmO87NlM1h1PYwMCKQpUWaNyB20V26caNzFsXQDfyOdznLlHCih6g=="],
|
||||
|
||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"oxc-resolver": ["oxc-resolver@11.14.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.14.0", "@oxc-resolver/binding-android-arm64": "11.14.0", "@oxc-resolver/binding-darwin-arm64": "11.14.0", "@oxc-resolver/binding-darwin-x64": "11.14.0", "@oxc-resolver/binding-freebsd-x64": "11.14.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.14.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.14.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.14.0", "@oxc-resolver/binding-linux-arm64-musl": "11.14.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.14.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.14.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.14.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.14.0", "@oxc-resolver/binding-linux-x64-gnu": "11.14.0", "@oxc-resolver/binding-linux-x64-musl": "11.14.0", "@oxc-resolver/binding-wasm32-wasi": "11.14.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.14.0", "@oxc-resolver/binding-win32-ia32-msvc": "11.14.0", "@oxc-resolver/binding-win32-x64-msvc": "11.14.0" } }, "sha512-i4wNrqhOd+4YdHJfHglHtFiqqSxXuzFA+RUqmmWN1aMD3r1HqUSrIhw17tSO4jwKfhLs9uw1wzFPmvMsWacStg=="],
|
||||
"oxc-parser": ["oxc-parser@0.142.0", "", { "dependencies": { "@oxc-project/types": "^0.142.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.142.0", "@oxc-parser/binding-android-arm64": "0.142.0", "@oxc-parser/binding-darwin-arm64": "0.142.0", "@oxc-parser/binding-darwin-x64": "0.142.0", "@oxc-parser/binding-freebsd-x64": "0.142.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.142.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.142.0", "@oxc-parser/binding-linux-arm64-gnu": "0.142.0", "@oxc-parser/binding-linux-arm64-musl": "0.142.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.142.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.142.0", "@oxc-parser/binding-linux-riscv64-musl": "0.142.0", "@oxc-parser/binding-linux-s390x-gnu": "0.142.0", "@oxc-parser/binding-linux-x64-gnu": "0.142.0", "@oxc-parser/binding-linux-x64-musl": "0.142.0", "@oxc-parser/binding-openharmony-arm64": "0.142.0", "@oxc-parser/binding-wasm32-wasi": "0.142.0", "@oxc-parser/binding-win32-arm64-msvc": "0.142.0", "@oxc-parser/binding-win32-ia32-msvc": "0.142.0", "@oxc-parser/binding-win32-x64-msvc": "0.142.0" } }, "sha512-kKR+jPiRJYJDexVoziIg/FVGvr1fT1FZSSJOk6tVoMKKSlsf1Cso+cgGCJkOEDWOP174vRntCPFKg+AS7InWvw=="],
|
||||
|
||||
"oxc-resolver": ["oxc-resolver@11.24.2", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.24.2", "@oxc-resolver/binding-android-arm64": "11.24.2", "@oxc-resolver/binding-darwin-arm64": "11.24.2", "@oxc-resolver/binding-darwin-x64": "11.24.2", "@oxc-resolver/binding-freebsd-x64": "11.24.2", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-musl": "11.24.2", "@oxc-resolver/binding-openharmony-arm64": "11.24.2", "@oxc-resolver/binding-wasm32-wasi": "11.24.2", "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
"picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||
|
||||
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
|
||||
"pidtree": ["pidtree@1.0.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-avfAvjB9Dd0wdj3rjJX//yS+G79OO0KrS5pJHFJENjYGX6N4SMgEDBBI/yFy0lloOYSaC6XQxzpOAMPfSYFV/Q=="],
|
||||
|
||||
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
|
||||
|
||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||
|
||||
"postcss-import": ["postcss-import@16.1.1", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ=="],
|
||||
|
||||
"postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
|
||||
"postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"prettier": ["prettier@3.7.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg=="],
|
||||
"prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="],
|
||||
|
||||
"prism-react-renderer": ["prism-react-renderer@2.4.1", "", { "dependencies": { "@types/prismjs": "^1.26.0", "clsx": "^2.0.0" }, "peerDependencies": { "react": ">=16.0.0" } }, "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||
|
||||
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
|
||||
"read-package-json-fast": ["read-package-json-fast@6.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^6.0.0", "npm-normalize-package-bin": "^6.0.0" } }, "sha512-PNaGjoCnw9DBA2Kl8D+8po957z778q/HOPuY2u3Bkw/JO3eC8MDx7jn/PgMtSgpcBbs+6UOjDbwReGpXmRvs0g=="],
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
|
||||
|
||||
"read-package-json-fast": ["read-package-json-fast@4.0.0", "", { "dependencies": { "json-parse-even-better-errors": "^4.0.0", "npm-normalize-package-bin": "^4.0.0" } }, "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg=="],
|
||||
"sanitize-filename": ["sanitize-filename@1.6.4", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg=="],
|
||||
|
||||
"resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
|
||||
|
||||
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
||||
|
||||
"sanitize-filename": ["sanitize-filename@1.6.3", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg=="],
|
||||
|
||||
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
||||
"shell-quote": ["shell-quote@1.10.0", "", {}, "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA=="],
|
||||
|
||||
"smol-toml": ["smol-toml@1.5.2", "", {}, "sha512-QlaZEqcAH3/RtNyet1IPIYPsEWAaYyXXv1Krsi+1L/QHppjX4Ifm8MQsBISz9vE8cHicIq3clogsheili5vhaQ=="],
|
||||
"smol-toml": ["smol-toml@1.7.1", "", {}, "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
|
||||
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
|
||||
@@ -557,29 +551,27 @@
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
|
||||
"synckit": ["synckit@0.11.13", "", { "dependencies": { "@pkgr/core": "^0.3.6" } }, "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg=="],
|
||||
|
||||
"synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="],
|
||||
|
||||
"tailwind-csstree": ["tailwind-csstree@0.1.4", "", {}, "sha512-FzD187HuFIZEyeR7Xy6sJbJll2d4SybS90satC8SKIuaNRC05CxMvdzN7BUsfDQffcnabckRM5OIcfArjsZ0mg=="],
|
||||
"tailwind-csstree": ["tailwind-csstree@0.3.3", "", { "peerDependencies": { "@eslint/css": ">=1.0.0" }, "optionalPeers": ["@eslint/css"] }, "sha512-je9J5UYRsTJqAjYrIBMMlge8T/rreRd44pJxgG5Zx/zeo4kAC/liUKqzztRZrGlYRJLvIf2Cb1DVJMTXSzEShA=="],
|
||||
|
||||
"tailwind-scrollbar": ["tailwind-scrollbar@4.0.2", "", { "dependencies": { "prism-react-renderer": "^2.4.1" }, "peerDependencies": { "tailwindcss": "4.x" } }, "sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA=="],
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
|
||||
"tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
|
||||
|
||||
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
|
||||
|
||||
"tar": ["tar@7.5.2", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg=="],
|
||||
"tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||
|
||||
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
|
||||
|
||||
"token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="],
|
||||
"token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
|
||||
|
||||
"truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
|
||||
|
||||
@@ -591,19 +583,23 @@
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.48.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.0", "@typescript-eslint/parser": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw=="],
|
||||
"typescript-eslint": ["typescript-eslint@8.67.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.67.0", "@typescript-eslint/parser": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", "@typescript-eslint/utils": "8.67.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg=="],
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
"unbash": ["unbash@4.0.10", "", {}, "sha512-b7zoBQvpWp0vuN5q2vK2RRBR2SvuruQAs50DApdDveBSn3eSYd84IaHodFqQIMlvY9K2VnyBUEXgwOBuGU9GBg=="],
|
||||
|
||||
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="],
|
||||
|
||||
"valibot": ["valibot@1.4.2", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg=="],
|
||||
|
||||
"walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="],
|
||||
|
||||
"which": ["which@5.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ=="],
|
||||
"which": ["which@7.0.0", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
@@ -613,54 +609,90 @@
|
||||
|
||||
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
|
||||
|
||||
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zod": ["zod@4.1.13", "", {}, "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig=="],
|
||||
"zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
|
||||
|
||||
"@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
"@eslint/config-array/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
|
||||
|
||||
"@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.5", "", { "dependencies": { "@tybys/wasm-util": "^0.10.2" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
"@typescript-eslint/typescript-estree/tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
|
||||
|
||||
"bun-types/@types/node": ["@types/node@24.13.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"eslint/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
"lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"npm-run-all2/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"tsconfig-paths-webpack-plugin/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
"tsconfig-paths-webpack-plugin/enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="],
|
||||
|
||||
"tsconfig-paths-webpack-plugin/tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"@eslint/config-array/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||
|
||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"eslint/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"tsconfig-paths-webpack-plugin/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[test]
|
||||
coverage = true
|
||||
coveragePathIgnorePatterns = [
|
||||
"tests/converters/helpers/commonTests.ts"
|
||||
]
|
||||
@@ -1,3 +1,5 @@
|
||||
# This compose file is for development and testing purposes only. See README for production deployment instructions.
|
||||
|
||||
services:
|
||||
convertx:
|
||||
build:
|
||||
|
||||
+21
-21
@@ -1,33 +1,26 @@
|
||||
import js from "@eslint/js";
|
||||
import eslintParserTypeScript from "@typescript-eslint/parser";
|
||||
import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
|
||||
import { defineConfig } from "eslint/config";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import tseslint, { parser as eslintParserTypeScript } from "typescript-eslint";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export default tseslint.config(
|
||||
export default defineConfig(
|
||||
{
|
||||
ignores: ["**/node_modules/**", "eslint.config.ts", "dist/**"],
|
||||
ignores: ["**/node_modules/**", "dist/**"],
|
||||
},
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
{
|
||||
files: ["**/*.{tsx,ts}"],
|
||||
plugins: {
|
||||
"better-tailwindcss": eslintPluginBetterTailwindcss,
|
||||
},
|
||||
files: ["**/*.{ts,tsx,cts,mts}"],
|
||||
extends: [
|
||||
eslintPluginBetterTailwindcss.configs.recommended,
|
||||
eslintPluginBetterTailwindcss.configs.stylistic,
|
||||
],
|
||||
languageOptions: {
|
||||
parser: eslintParserTypeScript,
|
||||
parserOptions: {
|
||||
project: "./tsconfig.eslint.json",
|
||||
tsconfigRootDir: __dirname,
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
globals: {
|
||||
...globals.node,
|
||||
@@ -39,9 +32,6 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
...(eslintPluginBetterTailwindcss.configs["recommended-warn"] ?? {}).rules,
|
||||
...(eslintPluginBetterTailwindcss.configs["stylistic-warn"] ?? {}).rules,
|
||||
// "tailwindcss/classnames-order": "off",
|
||||
"better-tailwindcss/enforce-consistent-line-wrapping": [
|
||||
"warn",
|
||||
{
|
||||
@@ -49,7 +39,7 @@ export default tseslint.config(
|
||||
printWidth: 100,
|
||||
},
|
||||
],
|
||||
"better-tailwindcss/no-unregistered-classes": [
|
||||
"better-tailwindcss/no-unknown-classes": [
|
||||
"warn",
|
||||
{
|
||||
ignore: [
|
||||
@@ -66,6 +56,16 @@ export default tseslint.config(
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.{jsx,tsx}"],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.{js,cjs,mjs,jsx}"],
|
||||
extends: [tseslint.configs.disableTypeChecked],
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/knip@5/schema.json",
|
||||
"$schema": "https://unpkg.com/knip@6/schema.json",
|
||||
"entry": ["tests/**/*.test.ts"],
|
||||
"project": ["src/**/*.ts", "src/**/*.tsx", "tests/**/*.ts"],
|
||||
"project": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.css", "tests/**/*.ts"],
|
||||
"ignoreBinaries": [
|
||||
"pandoc",
|
||||
"ffmpeg",
|
||||
"vips",
|
||||
"magick",
|
||||
"gm",
|
||||
"inkscape",
|
||||
"djxl",
|
||||
"dasel",
|
||||
"xelatex",
|
||||
"resvg",
|
||||
"assimp",
|
||||
"ebook-convert",
|
||||
"heif-info",
|
||||
"potrace",
|
||||
"soffice",
|
||||
"perl"
|
||||
],
|
||||
"tailwind": {
|
||||
"entry": ["src/main.css"]
|
||||
}
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
bun = "1.2.2"
|
||||
|
||||
[env]
|
||||
JWT_SECRET = "JustForDevelopmentPurposesOnlyChangeMeInProduction!"
|
||||
JWT_SECRET = "aLongAndSecretStringUsedToSignTheJSONWebToken1234"
|
||||
FFMPEG_OUTPUT_ARGS = "-preset veryfast -threads 2"
|
||||
|
||||
+24
-24
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "convertx-frontend",
|
||||
"version": "0.15.0",
|
||||
"version": "0.18.0",
|
||||
"scripts": {
|
||||
"dev": "bun run --watch src/index.tsx",
|
||||
"hot": "bun run --hot src/index.tsx",
|
||||
@@ -13,16 +13,17 @@
|
||||
"lint:tsc": "tsc --noEmit",
|
||||
"lint:knip": "knip",
|
||||
"lint:eslint": "eslint .",
|
||||
"lint:prettier": "prettier --check ."
|
||||
"lint:prettier": "prettier --check .",
|
||||
"lint:xss": "xss-scan"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elysiajs/html": "^1.4.0",
|
||||
"@elysiajs/jwt": "^1.4.0",
|
||||
"@elysiajs/static": "^1.4.6",
|
||||
"@kitajs/html": "^4.2.11",
|
||||
"elysia": "^1.4.16",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"tar": "^7.5.2"
|
||||
"@elysiajs/html": "^1.4.2",
|
||||
"@elysiajs/jwt": "^1.4.2",
|
||||
"@elysiajs/static": "^1.4.10",
|
||||
"@kitajs/html": "^4.2.13",
|
||||
"elysia": "1.4.22",
|
||||
"sanitize-filename": "^1.6.4",
|
||||
"tar": "^7.5.22"
|
||||
},
|
||||
"module": "src/index.tsx",
|
||||
"type": "module",
|
||||
@@ -30,24 +31,23 @@
|
||||
"start": "bun run src/index.tsx"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@kitajs/ts-html-plugin": "^4.1.3",
|
||||
"@tailwindcss/cli": "^4.1.17",
|
||||
"@tailwindcss/postcss": "^4.1.17",
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@kitajs/ts-html-plugin": "^4.1.4",
|
||||
"@tailwindcss/cli": "^4.3.3",
|
||||
"@tailwindcss/postcss": "^4.3.3",
|
||||
"@types/bun": "latest",
|
||||
"@types/node": "^24.10.1",
|
||||
"@typescript-eslint/parser": "^8.46.4",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-better-tailwindcss": "^3.7.10",
|
||||
"globals": "^16.5.0",
|
||||
"knip": "^5.69.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"postcss": "^8.5.6",
|
||||
"prettier": "^3.6.2",
|
||||
"@types/node": "^24.13.3",
|
||||
"eslint": "^10.8.1",
|
||||
"eslint-plugin-better-tailwindcss": "^4.7.0",
|
||||
"globals": "^17.9.0",
|
||||
"knip": "^6.32.1",
|
||||
"npm-run-all2": "^9.0.3",
|
||||
"postcss": "^8.5.26",
|
||||
"prettier": "^3.9.6",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tailwindcss": "^4.3.3",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.46.4"
|
||||
"typescript-eslint": "^8.67.0"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ function handleFile(file) {
|
||||
<td>${file.name}</td>
|
||||
<td><progress max="100" class="inline-block h-2 appearance-none overflow-hidden rounded-full border-0 bg-neutral-700 bg-none text-accent-500 accent-accent-500 [&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full [&::-webkit-progress-value]:[background:none] [&[value]::-webkit-progress-value]:bg-accent-500 [&[value]::-webkit-progress-value]:transition-[inline-size]"></progress></td>
|
||||
<td>${(file.size / 1024).toFixed(2)} kB</td>
|
||||
<td><a onclick="deleteRow(this)">Remove</a></td>
|
||||
<td><button type="button" class="text-accent-500 hover:underline" onclick="deleteRow(this)">Remove</button></td>
|
||||
`;
|
||||
|
||||
if (!fileType) {
|
||||
|
||||
+16
-12
@@ -121,20 +121,24 @@ export async function convert(
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("assimp", ["export", filePath, targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
execFile(
|
||||
"assimp",
|
||||
["export", filePath, targetPath, `-f${convertTo}`],
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
resolve("Done");
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ExecFileFn } from "./types";
|
||||
export const properties = {
|
||||
from: {
|
||||
document: [
|
||||
"azw3",
|
||||
"azw4",
|
||||
"chm",
|
||||
"cbr",
|
||||
|
||||
@@ -11,6 +11,10 @@ export const properties = {
|
||||
},
|
||||
};
|
||||
|
||||
export function buildDaselArgs(filePath: string, fileType: string, convertTo: string): string[] {
|
||||
return ["--var", `data=${fileType}:file:${filePath}`, "--out", convertTo, "$data"];
|
||||
}
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
@@ -19,14 +23,10 @@ export async function convert(
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
const args: string[] = [];
|
||||
|
||||
args.push("--file", filePath);
|
||||
args.push("--read", fileType);
|
||||
args.push("--write", convertTo);
|
||||
const args = buildDaselArgs(filePath, fileType, convertTo);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("dasel", args, (error, stdout, stderr) => {
|
||||
const childProcess = execFile("dasel", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
return;
|
||||
@@ -44,5 +44,7 @@ export async function convert(
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
childProcess?.stdin?.end();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
import {
|
||||
execFile as execFileOriginal,
|
||||
type ChildProcess,
|
||||
type ExecFileOptions,
|
||||
} from "node:child_process";
|
||||
|
||||
// ffmpeg streams continuous progress to stderr, so a long conversion overflows
|
||||
// execFile's 1 MB default maxBuffer and fails with "stderr maxBuffer length
|
||||
// exceeded" (issue #565). Raise it well above that. The options object must be
|
||||
// passed before the callback: execFile ignores an options argument placed after
|
||||
// the callback, which is why the shared ExecFileFn type cannot carry it.
|
||||
const FFMPEG_MAX_BUFFER = 1024 * 1024 * 64; // 64 MB
|
||||
|
||||
type FfmpegExecFile = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
options: ExecFileOptions,
|
||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||
) => ChildProcess | void;
|
||||
|
||||
// This could be done dynamically by running `ffmpeg -formats` and parsing the output
|
||||
export const properties = {
|
||||
@@ -224,6 +241,7 @@ export const properties = {
|
||||
"lxf",
|
||||
"m15",
|
||||
"m2a",
|
||||
"m2ts",
|
||||
"m4a",
|
||||
"m4b",
|
||||
"m4v",
|
||||
@@ -406,6 +424,7 @@ export const properties = {
|
||||
"tiff",
|
||||
"tmv",
|
||||
"truehd",
|
||||
"ts",
|
||||
"tta",
|
||||
"tty",
|
||||
"txd",
|
||||
@@ -439,6 +458,7 @@ export const properties = {
|
||||
"vivo",
|
||||
"vmd",
|
||||
"vobsub",
|
||||
"vob",
|
||||
"voc",
|
||||
"vpk",
|
||||
"vplayer",
|
||||
@@ -456,6 +476,8 @@ export const properties = {
|
||||
"webm",
|
||||
"webp",
|
||||
"webvtt",
|
||||
"wma",
|
||||
"wmv",
|
||||
"wow",
|
||||
"wsaud",
|
||||
"wsd",
|
||||
@@ -588,6 +610,7 @@ export const properties = {
|
||||
"m4v",
|
||||
"mjpeg",
|
||||
"mjpg",
|
||||
"mka",
|
||||
"mkv",
|
||||
"mlp",
|
||||
"mmf",
|
||||
@@ -693,13 +716,13 @@ export async function convert(
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
execFile: FfmpegExecFile = execFileOriginal as FfmpegExecFile, // to make it mockable
|
||||
): Promise<string> {
|
||||
let extraArgs: string[] = [];
|
||||
let message = "Done";
|
||||
|
||||
if (convertTo === "ico") {
|
||||
// make sure image is 256x256 or smaller
|
||||
// Make sure image is 256x256 or smaller
|
||||
extraArgs = [
|
||||
"-filter:v",
|
||||
"scale='min(256,iw)':min'(256,ih)':force_original_aspect_ratio=decrease",
|
||||
@@ -708,7 +731,7 @@ export async function convert(
|
||||
}
|
||||
|
||||
if (convertTo.split(".").length > 1) {
|
||||
// support av1.mkv and av1.mp4 and h265.mp4 etc.
|
||||
// Support av1.mkv and av1.mp4 and h265.mp4 etc.
|
||||
const split = convertTo.split(".");
|
||||
const codec_short = split[0];
|
||||
|
||||
@@ -730,11 +753,15 @@ export async function convert(
|
||||
|
||||
// Parse FFMPEG_ARGS environment variable into array
|
||||
const ffmpegArgs = process.env.FFMPEG_ARGS ? process.env.FFMPEG_ARGS.split(/\s+/) : [];
|
||||
const ffmpegOutputArgs = process.env.FFMPEG_OUTPUT_ARGS
|
||||
? process.env.FFMPEG_OUTPUT_ARGS.split(/\s+/)
|
||||
: [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(
|
||||
"ffmpeg",
|
||||
[...ffmpegArgs, "-i", filePath, ...extraArgs, targetPath],
|
||||
[...ffmpegArgs, "-i", filePath, ...ffmpegOutputArgs, ...extraArgs, targetPath],
|
||||
{ maxBuffer: FFMPEG_MAX_BUFFER },
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
|
||||
@@ -317,8 +317,10 @@ export function convert(
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
// Apply EXIF orientation so photos (e.g. from phones) don't end up sideways
|
||||
// when converted to formats where the orientation tag is lost or ignored
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("gm", ["convert", filePath, targetPath], (error, stdout, stderr) => {
|
||||
execFile("gm", ["convert", filePath, "-auto-orient", targetPath], (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
}
|
||||
|
||||
@@ -468,6 +468,10 @@ export function convert(
|
||||
outputArgs.push("-background", "white", "-alpha", "remove");
|
||||
}
|
||||
|
||||
// Apply EXIF orientation so photos (e.g. from phones) don't end up sideways
|
||||
// when converted to formats where the orientation tag is lost or ignored
|
||||
outputArgs.push("-auto-orient");
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile(
|
||||
"magick",
|
||||
|
||||
@@ -6,7 +6,6 @@ export const properties = {
|
||||
text: [
|
||||
"602",
|
||||
"abw",
|
||||
"csv",
|
||||
"cwk",
|
||||
"doc",
|
||||
"docm",
|
||||
@@ -35,7 +34,6 @@ export const properties = {
|
||||
"stw",
|
||||
"sxw",
|
||||
"tab",
|
||||
"tsv",
|
||||
"txt",
|
||||
"wn",
|
||||
"wpd",
|
||||
@@ -46,10 +44,10 @@ export const properties = {
|
||||
"xml",
|
||||
"zabw",
|
||||
],
|
||||
calc: ["csv", "ods", "tsv", "xls", "xlsx"],
|
||||
},
|
||||
to: {
|
||||
text: [
|
||||
"csv",
|
||||
"doc",
|
||||
"docm",
|
||||
"docx",
|
||||
@@ -65,19 +63,19 @@ export const properties = {
|
||||
"pdf",
|
||||
"rtf",
|
||||
"tab",
|
||||
"tsv",
|
||||
"txt",
|
||||
"wps",
|
||||
"wpt",
|
||||
"xhtml",
|
||||
"xml",
|
||||
],
|
||||
calc: ["csv", "ods", "pdf", "tsv", "xls", "xlsx", "ots", "xlsm", "xlt", "xltm"],
|
||||
},
|
||||
};
|
||||
|
||||
type FileCategories = "text" | "calc";
|
||||
|
||||
const filters: Record<FileCategories, Record<string, string>> = {
|
||||
const filters: Record<FileCategories, Record<string, string | null>> = {
|
||||
text: {
|
||||
"602": "T602Document",
|
||||
abw: "AbiWord",
|
||||
@@ -102,7 +100,7 @@ const filters: Record<FileCategories, Record<string, string>> = {
|
||||
odt: "writer8",
|
||||
ott: "writer8_template",
|
||||
pages: "Apple Pages",
|
||||
// pdf: "writer_pdf_import",
|
||||
pdf: "writer_pdf_import",
|
||||
psw: "PocketWord File",
|
||||
rtf: "Rich Text Format",
|
||||
sdw: "StarOffice_Writer",
|
||||
@@ -113,18 +111,44 @@ const filters: Record<FileCategories, Record<string, string>> = {
|
||||
txt: "Text",
|
||||
wn: "WriteNow",
|
||||
wpd: "WordPerfect",
|
||||
wps: "MS Word 97",
|
||||
// .wps is Microsoft Works, not MS Word 97/.doc - forcing the "MS Word 97"
|
||||
// filter makes soffice reject a genuine Works document with "source file
|
||||
// could not be loaded", even though it converts the same file fine with
|
||||
// no --infilter at all (LibreOffice auto-detects it correctly).
|
||||
|
||||
// null is deliberate for BOTH directions here, not just the import side:
|
||||
// this map feeds both --infilter (import) and the --convert-to suffix
|
||||
// (export). On import, null lets LibreOffice auto-detect - its Works
|
||||
// import filter (MS_Works, libwps-backed) is import-only, so it can only
|
||||
// be reached via auto-detection anyway. On export, LibreOffice has no
|
||||
// Works export filter at all; bare `--convert-to wps` falls back to its
|
||||
// default export filter for the extension, which is "MS Word 97" - the
|
||||
// exact filter this map pinned before, so export output is unchanged.
|
||||
wps: null,
|
||||
wpt: "MS Word 97 Vorlage",
|
||||
wri: "MS_Write",
|
||||
xhtml: "HTML (StarWriter)",
|
||||
xml: "OpenDocument Text Flat XML",
|
||||
zabw: "AbiWord",
|
||||
},
|
||||
calc: {},
|
||||
calc: {
|
||||
csv: "Text - txt - csv (StarCalc)",
|
||||
ods: "calc8",
|
||||
ots: "calc8_template",
|
||||
pdf: "calc_pdf_Export",
|
||||
tsv: "Text - txt - csv (StarCalc)",
|
||||
xls: "MS Excel 97",
|
||||
xlsx: "Calc MS Excel 2007 XML",
|
||||
xlsm: "Calc MS Excel 2007 XML VBA",
|
||||
xlt: "MS Excel 97 Vorlage",
|
||||
xltm: "Calc MS Excel 2007 XML Template",
|
||||
},
|
||||
};
|
||||
|
||||
const getFilters = (fileType: string, converto: string) => {
|
||||
if (fileType in filters.text && converto in filters.text) {
|
||||
if (converto === "pdf") {
|
||||
return [null, null];
|
||||
} else if (fileType in filters.text && converto in filters.text) {
|
||||
return [filters.text[fileType], filters.text[converto]];
|
||||
} else if (fileType in filters.calc && converto in filters.calc) {
|
||||
return [filters.calc[fileType], filters.calc[converto]];
|
||||
@@ -148,7 +172,7 @@ export function convert(
|
||||
const [inFilter, outFilter] = getFilters(fileType, convertTo);
|
||||
|
||||
if (inFilter) {
|
||||
args.push(`--infilter="${inFilter}"`);
|
||||
args.push(`--infilter=${inFilter}`);
|
||||
}
|
||||
|
||||
if (outFilter) {
|
||||
@@ -175,3 +199,9 @@ export function convert(
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal For testing only. Do not use in production.
|
||||
* Tests need direct access to cover all filters.
|
||||
*/
|
||||
export { filters, getFilters };
|
||||
|
||||
+31
-5
@@ -18,11 +18,14 @@ import { convert as convertLibjxl, properties as propertiesLibjxl } from "./libj
|
||||
import { convert as convertLibreOffice, properties as propertiesLibreOffice } from "./libreoffice";
|
||||
import { convert as convertMsgconvert, properties as propertiesMsgconvert } from "./msgconvert";
|
||||
import { convert as convertPandoc, properties as propertiesPandoc } from "./pandoc";
|
||||
import { convert as convertPdftops, properties as propertiesPdftops } from "./pdftops";
|
||||
import { convert as convertPotrace, properties as propertiesPotrace } from "./potrace";
|
||||
import { convert as convertresvg, properties as propertiesresvg } from "./resvg";
|
||||
import { convert as convertImage, properties as propertiesImage } from "./vips";
|
||||
import { convert as convertVtracer, properties as propertiesVtracer } from "./vtracer";
|
||||
import { convert as convertVcf, properties as propertiesVcf } from "./vcf";
|
||||
import { convert as convertxelatex, properties as propertiesxelatex } from "./xelatex";
|
||||
import { convert as convertMarkitdown, properties as propertiesMarkitdown } from "./markitdown";
|
||||
|
||||
// This should probably be reconstructed so that the functions are not imported instead the functions hook into this to make the converters more modular
|
||||
|
||||
@@ -127,6 +130,18 @@ const properties: Record<
|
||||
properties: propertiesVtracer,
|
||||
converter: convertVtracer,
|
||||
},
|
||||
vcf: {
|
||||
properties: propertiesVcf,
|
||||
converter: convertVcf,
|
||||
},
|
||||
markitDown: {
|
||||
properties: propertiesMarkitdown,
|
||||
converter: convertMarkitdown,
|
||||
},
|
||||
pdftops: {
|
||||
properties: propertiesPdftops,
|
||||
converter: convertPdftops,
|
||||
},
|
||||
};
|
||||
|
||||
function chunks<T>(arr: T[], size: number): T[][] {
|
||||
@@ -154,13 +169,18 @@ export async function handleConvert(
|
||||
const toProcess: Promise<string>[] = [];
|
||||
for (const fileName of chunk) {
|
||||
const filePath = `${userUploadsDir}${fileName}`;
|
||||
const fileTypeOrig = fileName.split(".").pop() ?? "";
|
||||
const fileTypeOrig = fileName.includes(".") ? (fileName.split(".").pop() ?? "") : "";
|
||||
const fileType = normalizeFiletype(fileTypeOrig);
|
||||
const newFileExt = normalizeOutputFiletype(convertTo);
|
||||
const newFileName = fileName.replace(
|
||||
new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
|
||||
newFileExt,
|
||||
);
|
||||
let newFileName: string;
|
||||
if (fileTypeOrig === "") {
|
||||
newFileName = `${fileName}.${newFileExt}`;
|
||||
} else {
|
||||
newFileName = fileName.replace(
|
||||
new RegExp(`${fileTypeOrig}(?!.*${fileTypeOrig})`),
|
||||
newFileExt,
|
||||
);
|
||||
}
|
||||
const targetPath = `${userOutputDir}${newFileName}`;
|
||||
toProcess.push(
|
||||
new Promise((resolve, reject) => {
|
||||
@@ -327,3 +347,9 @@ for (const converterName in properties) {
|
||||
export const getAllInputs = (converter: string) => {
|
||||
return allInputs[converter] || [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal For testing only. Do not use in production.
|
||||
* Tests need direct access to cover all branches of converter discovery and chunking logic.
|
||||
*/
|
||||
export { chunks, mainConverter };
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
document: ["pdf", "powerpoint", "excel", "docx", "pptx", "html"],
|
||||
},
|
||||
to: {
|
||||
document: ["md"],
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal,
|
||||
): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("markitdown", [filePath, "-o", targetPath], (err, stdout, stderr) => {
|
||||
if (err) {
|
||||
reject(`markitdown error: ${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { execFile as execFileOriginal } from "node:child_process";
|
||||
import { ExecFileFn } from "./types";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
document: ["pdf"],
|
||||
},
|
||||
to: {
|
||||
document: ["eps", "ps"],
|
||||
},
|
||||
};
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown,
|
||||
execFile: ExecFileFn = execFileOriginal, // to make it mockable
|
||||
): Promise<string> {
|
||||
const args: string[] = [];
|
||||
|
||||
if (convertTo === "eps") {
|
||||
args.push("-eps");
|
||||
}
|
||||
|
||||
args.push(filePath, targetPath);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
execFile("pdftops", args, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(`error: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`stdout: ${stdout}`);
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
resolve("Done");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { ExecFileOptions } from "child_process";
|
||||
import type { ChildProcess, ExecFileOptions } from "child_process";
|
||||
|
||||
export type ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||
options?: ExecFileOptions,
|
||||
) => void;
|
||||
) => ChildProcess | void;
|
||||
|
||||
export type ConvertFnWithExecFile = (
|
||||
filePath: string,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { readFile, writeFile } from "fs/promises";
|
||||
|
||||
export const properties = {
|
||||
from: {
|
||||
contacts: ["vcf"],
|
||||
},
|
||||
to: {
|
||||
contacts: ["csv"],
|
||||
},
|
||||
};
|
||||
|
||||
export function parseVCF(data: string): Record<string, string>[] {
|
||||
const cards = data
|
||||
.split(/BEGIN:VCARD/)
|
||||
.slice(1)
|
||||
.map((card) => card.split(/END:VCARD/)[0])
|
||||
.filter((card) => card);
|
||||
return cards
|
||||
.map((card) => {
|
||||
if (!card) return {};
|
||||
const lines = card.split("\n").filter((line) => line.trim());
|
||||
const contact: Record<string, string> = {};
|
||||
for (const line of lines) {
|
||||
const colonIndex = line.indexOf(":");
|
||||
if (colonIndex === -1) continue;
|
||||
const key = line.slice(0, colonIndex).trim();
|
||||
const value = line.slice(colonIndex + 1).trim();
|
||||
if (key === "FN") {
|
||||
contact["Full Name"] = value;
|
||||
} else if (key === "N") {
|
||||
const parts = value.split(";");
|
||||
contact["Last Name"] = parts[0] || "";
|
||||
contact["First Name"] = parts[1] || "";
|
||||
} else if (key.startsWith("TEL")) {
|
||||
contact["Phone"] = value;
|
||||
} else if (key.startsWith("EMAIL")) {
|
||||
contact["Email"] = value;
|
||||
} else if (key === "ORG") {
|
||||
contact["Organization"] = value.split(";")[0] || "";
|
||||
}
|
||||
}
|
||||
return contact;
|
||||
})
|
||||
.filter((contact) => Object.keys(contact).length > 0);
|
||||
}
|
||||
|
||||
export function toCSV(data: Record<string, string>[]): string {
|
||||
if (!data.length) return "";
|
||||
const first = data[0];
|
||||
if (!first) return "";
|
||||
const headers = Object.keys(first);
|
||||
const escape = (str: string) => `"${str.replace(/"/g, '""')}"`;
|
||||
const rows = data.map((row) => headers.map((h) => escape(row[h] || "")).join(","));
|
||||
return [headers.join(","), ...rows].join("\n");
|
||||
}
|
||||
|
||||
export async function convert(
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
convertTo: string,
|
||||
targetPath: string,
|
||||
options?: unknown, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
): Promise<string> {
|
||||
const vcfData = await readFile(filePath, "utf-8");
|
||||
const contacts = parseVCF(vcfData);
|
||||
const csvData = toCSV(contacts);
|
||||
await writeFile(targetPath, csvData, "utf-8");
|
||||
return "Done";
|
||||
}
|
||||
+51
-36
@@ -1,43 +1,58 @@
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { Database } from "bun:sqlite";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
|
||||
mkdirSync("./data", { recursive: true });
|
||||
const db = new Database("./data/mydb.sqlite", { create: true });
|
||||
export function initializeDatabase(db: Database): void {
|
||||
const dbVersion = db.query("PRAGMA user_version").get() as { user_version?: number };
|
||||
const hasTables = db.query("SELECT * FROM sqlite_master WHERE type='table'").get();
|
||||
|
||||
if (!db.query("SELECT * FROM sqlite_master WHERE type='table'").get()) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS file_names (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
job_id INTEGER NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
output_file_name TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'not started',
|
||||
FOREIGN KEY (job_id) REFERENCES jobs(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
date_created TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'not started',
|
||||
num_files INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
PRAGMA user_version = 1;`);
|
||||
if (!hasTables) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS file_names (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
job_id INTEGER NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
output_file_name TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'not started',
|
||||
FOREIGN KEY (job_id) REFERENCES jobs(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
date_created TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'not started',
|
||||
num_files INTEGER DEFAULT 0,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
`);
|
||||
db.exec("PRAGMA user_version = 1;");
|
||||
} else if ((dbVersion?.user_version ?? 0) < 1) {
|
||||
// Don't trust user_version alone — verify the column is actually
|
||||
// missing before altering. This makes the migration safe to re-run
|
||||
// even against a file left in an inconsistent state.
|
||||
const columns = db.query("PRAGMA table_info(file_names)").all() as { name: string }[];
|
||||
const hasStatusColumn = columns.some((c) => c.name.toLowerCase() === "status");
|
||||
|
||||
if (!hasStatusColumn) {
|
||||
db.exec("ALTER TABLE file_names ADD COLUMN status TEXT DEFAULT 'not started';");
|
||||
}
|
||||
|
||||
db.exec("PRAGMA user_version = 1;");
|
||||
console.log("Updated database to version 1.");
|
||||
}
|
||||
|
||||
// enable WAL mode
|
||||
db.exec("PRAGMA journal_mode = WAL;");
|
||||
}
|
||||
|
||||
const dbVersion = (db.query("PRAGMA user_version").get() as { user_version?: number }).user_version;
|
||||
if (dbVersion === 0) {
|
||||
db.exec("ALTER TABLE file_names ADD COLUMN status TEXT DEFAULT 'not started';");
|
||||
db.exec("PRAGMA user_version = 1;");
|
||||
console.log("Updated database to version 1.");
|
||||
}
|
||||
|
||||
// enable WAL mode
|
||||
db.exec("PRAGMA journal_mode = WAL;");
|
||||
const dbPath = process.env.DB_PATH ?? "./data/mydb.sqlite";
|
||||
mkdirSync(dirname(dbPath), { recursive: true });
|
||||
const db = new Database(dbPath, { create: true });
|
||||
initializeDatabase(db);
|
||||
|
||||
export default db;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function buildDownloadUrl(webroot: string, outputPath: string, fileName: string): string {
|
||||
return `${webroot}/download/${outputPath}${encodeURIComponent(fileName)}`;
|
||||
}
|
||||
@@ -23,3 +23,5 @@ export const MAX_CONVERT_PROCESS =
|
||||
|
||||
export const UNAUTHENTICATED_USER_SHARING =
|
||||
process.env.UNAUTHENTICATED_USER_SHARING?.toLowerCase() === "true" || false;
|
||||
|
||||
export const TIMEZONE = process.env.TZ || undefined;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export function isHtmlPageRequest(request: Request): boolean {
|
||||
const acceptsHtml = request.headers.get("accept")?.toLowerCase().includes("text/html");
|
||||
return (request.method === "GET" || request.method === "HEAD") && Boolean(acceptsHtml);
|
||||
}
|
||||
@@ -31,6 +31,24 @@ export const normalizeOutputFiletype = (filetype: string): string => {
|
||||
case "markdown_mmd":
|
||||
case "markdown":
|
||||
return "md";
|
||||
// assimp format ids that aren't real file extensions — map to the
|
||||
// canonical extension for the underlying format so the output file
|
||||
// opens in third-party viewers. The format id is still passed to
|
||||
// `assimp export -f<id>` so the right encoding/variant is produced.
|
||||
case "glb2":
|
||||
return "glb";
|
||||
case "gltf2":
|
||||
return "gltf";
|
||||
case "objnomtl":
|
||||
return "obj";
|
||||
case "stlb":
|
||||
return "stl";
|
||||
case "plyb":
|
||||
return "ply";
|
||||
case "fbxa":
|
||||
return "fbx";
|
||||
case "assjson":
|
||||
return "json";
|
||||
default:
|
||||
return lowercaseFiletype;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { exec } from "node:child_process";
|
||||
import { readFile } from "node:fs";
|
||||
import { version } from "../../package.json";
|
||||
|
||||
console.log(`ConvertX v${version}`);
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
exec("cat /etc/os-release", (error, stdout) => {
|
||||
readFile("/etc/os-release", "utf8", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Not running on docker, this is not supported.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -17,6 +19,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("pandoc -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Pandoc is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -27,6 +30,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("ffmpeg -version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("FFmpeg is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -37,6 +41,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("vips -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Vips is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -47,6 +52,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("magick --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("ImageMagick is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -57,6 +63,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("gm version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("GraphicsMagick is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -67,6 +74,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("inkscape --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Inkscape is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -77,6 +85,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("djxl --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libjxl-tools is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -84,19 +93,21 @@ if (process.env.NODE_ENV === "production") {
|
||||
}
|
||||
});
|
||||
|
||||
exec("dasel --version", (error, stdout) => {
|
||||
exec("dasel version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("dasel is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
console.log(`dasel ${stdout.split("\n")[0]}`);
|
||||
}
|
||||
});
|
||||
|
||||
exec("xelatex -version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Tex Live with XeTeX is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -107,6 +118,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("resvg -V", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("resvg is not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -117,16 +129,19 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("assimp version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("assimp is not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(`assimp ${stdout.split("\n")[5]}`);
|
||||
const firstLines = stdout.split("\n");
|
||||
console.log(`assimp ${firstLines[5] || firstLines[0] || ""}`);
|
||||
}
|
||||
});
|
||||
|
||||
exec("ebook-convert --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("ebook-convert (calibre) is not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -137,6 +152,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("heif-info -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libheif is not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -147,6 +163,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("potrace -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("potrace is not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -157,6 +174,7 @@ if (process.env.NODE_ENV === "production") {
|
||||
exec("soffice --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("libreoffice is not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
@@ -164,19 +182,25 @@ if (process.env.NODE_ENV === "production") {
|
||||
}
|
||||
});
|
||||
|
||||
exec("msgconvert --version", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("msgconvert (libemail-outlook-message-perl) is not installed");
|
||||
}
|
||||
// msgconvert has no version flag, so read the version of the perl module providing it
|
||||
exec(
|
||||
"perl -MEmail::Outlook::Message -e 'print $Email::Outlook::Message::VERSION'",
|
||||
(error, stdout) => {
|
||||
if (error) {
|
||||
console.error("msgconvert (libemail-outlook-message-perl) is not installed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
console.log(stdout.split("\n")[0]);
|
||||
}
|
||||
});
|
||||
if (stdout) {
|
||||
console.log(`msgconvert v${stdout.split("\n")[0]}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
exec("bun -v", (error, stdout) => {
|
||||
if (error) {
|
||||
console.error("Bun is not installed. wait what");
|
||||
return;
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
|
||||
+6
-2
@@ -50,7 +50,11 @@ const app = new Elysia({
|
||||
.use(listConverters)
|
||||
.use(chooseConverter)
|
||||
.use(healthcheck)
|
||||
.onError(({ error }) => {
|
||||
.onError(({ error, code, request }) => {
|
||||
if (code === "NOT_FOUND") {
|
||||
console.warn(`404: ${request.method} ${new URL(request.url).pathname}`);
|
||||
return;
|
||||
}
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
@@ -65,7 +69,7 @@ if (process.env.NODE_ENV !== "production") {
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(3000);
|
||||
app.listen(process.env.PORT || 3000);
|
||||
|
||||
console.log(`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}${WEBROOT}`);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export const chooseConverter = new Elysia().use(userService).post(
|
||||
<article
|
||||
class={`
|
||||
convert_to_popup absolute z-2 m-0 hidden h-[50vh] max-h-[50vh] w-full flex-col
|
||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||
overflow-x-hidden overflow-y-auto rounded-sm bg-neutral-800
|
||||
sm:h-[30vh]
|
||||
`}
|
||||
>
|
||||
@@ -28,7 +28,7 @@ export const chooseConverter = new Elysia().use(userService).post(
|
||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||
tabindex={0}
|
||||
class={`
|
||||
target rounded bg-neutral-700 p-1 text-base
|
||||
target rounded-sm bg-neutral-700 p-1 text-base
|
||||
hover:bg-neutral-600
|
||||
`}
|
||||
data-value={`${target},${converter}`}
|
||||
|
||||
@@ -47,7 +47,12 @@ export const convert = new Elysia().use(userService).post(
|
||||
const convertTo = normalizeFiletype(body.convert_to.split(",")[0] ?? "");
|
||||
const converterName = body.convert_to.split(",")[1];
|
||||
|
||||
if (!converterName) {
|
||||
if (
|
||||
!converterName ||
|
||||
convertTo.includes("/") ||
|
||||
convertTo.includes("\\") ||
|
||||
convertTo.includes("..")
|
||||
) {
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { uploadsDir } from "..";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
import sanitize from "sanitize-filename";
|
||||
import path from "node:path";
|
||||
|
||||
export const deleteFile = new Elysia().use(userService).post(
|
||||
"/delete",
|
||||
@@ -20,9 +22,12 @@ export const deleteFile = new Elysia().use(userService).post(
|
||||
return redirect(`${WEBROOT}/`, 302);
|
||||
}
|
||||
|
||||
const userUploadsDir = `${uploadsDir}${user.id}/${jobId.value}/`;
|
||||
const userUploadsDir = path.join(uploadsDir, user.id, jobId.value);
|
||||
|
||||
await unlink(`${userUploadsDir}${body.filename}`);
|
||||
const sanitized = sanitize(body.filename);
|
||||
const targetPath = path.join(userUploadsDir, sanitized);
|
||||
|
||||
await unlink(targetPath);
|
||||
|
||||
return {
|
||||
message: "File deleted successfully.",
|
||||
|
||||
+107
-28
@@ -1,38 +1,117 @@
|
||||
import { rmSync } from "node:fs";
|
||||
import { Elysia } from "elysia";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { outputDir, uploadsDir } from "..";
|
||||
import db from "../db/db";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
import { Jobs } from "../db/types";
|
||||
|
||||
export const deleteJob = new Elysia().use(userService).get(
|
||||
"/delete/:jobId",
|
||||
async ({ params, redirect, user }) => {
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
export const deleteJob = new Elysia()
|
||||
.use(userService)
|
||||
// SECURE: Use POST instead of GET for state-changing actions to prevent CSRF.
|
||||
// Browsers automatically follow GET links, which can lead to silent data deletion.
|
||||
.post(
|
||||
"/delete/:jobId",
|
||||
async ({ params, redirect, user }) => {
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, params.jobId);
|
||||
|
||||
if (!job) {
|
||||
return redirect(`${WEBROOT}/results`, 302);
|
||||
}
|
||||
if (!job) {
|
||||
return redirect(`${WEBROOT}/results`, 302);
|
||||
}
|
||||
|
||||
// delete the directories
|
||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
// delete the directories
|
||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
|
||||
// delete the job
|
||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||
return redirect(`${WEBROOT}/history`, 302);
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
},
|
||||
);
|
||||
// delete the job
|
||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||
return redirect(`${WEBROOT}/history`, 302);
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/delete-multiple",
|
||||
async ({ body, user, set }) => {
|
||||
const { jobIds } = body;
|
||||
|
||||
if (!Array.isArray(jobIds) || jobIds.length === 0) {
|
||||
set.status = 400;
|
||||
return { success: false, message: "Invalid job IDs provided" };
|
||||
}
|
||||
|
||||
const results = {
|
||||
success: [] as string[],
|
||||
failed: [] as { jobId: string; error: string }[],
|
||||
};
|
||||
|
||||
// Process deletions sequentially for safety
|
||||
for (const jobId of jobIds) {
|
||||
try {
|
||||
const job = db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
.as(Jobs)
|
||||
.get(user.id, jobId);
|
||||
|
||||
if (!job) {
|
||||
results.failed.push({
|
||||
jobId,
|
||||
error: "Job not found or unauthorized",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete the directories
|
||||
try {
|
||||
rmSync(`${outputDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete output directory for job ${jobId}:`, error);
|
||||
}
|
||||
|
||||
try {
|
||||
rmSync(`${uploadsDir}${job.user_id}/${job.id}`, {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to delete uploads directory for job ${jobId}:`, error);
|
||||
}
|
||||
|
||||
// Delete the job from database
|
||||
db.query("DELETE FROM jobs WHERE id = ?").run(job.id);
|
||||
results.success.push(jobId);
|
||||
} catch (error) {
|
||||
results.failed.push({
|
||||
jobId,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: results.failed.length === 0,
|
||||
deleted: results.success.length,
|
||||
failed: results.failed.length,
|
||||
details: results,
|
||||
};
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
body: t.Object({
|
||||
jobIds: t.Array(t.String(), { maxItems: 100 }),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ export const download = new Elysia()
|
||||
.use(userService)
|
||||
.get(
|
||||
"/download/:userId/:jobId/:fileName",
|
||||
async ({ params, redirect, user }) => {
|
||||
async ({ params, redirect, set, user }) => {
|
||||
const userId = user.id;
|
||||
const job = await db
|
||||
.query("SELECT * FROM jobs WHERE user_id = ? AND id = ?")
|
||||
@@ -25,7 +25,13 @@ export const download = new Elysia()
|
||||
const fileName = sanitize(decodeURIComponent(params.fileName));
|
||||
|
||||
const filePath = `${outputDir}${userId}/${jobId}/${fileName}`;
|
||||
return Bun.file(filePath);
|
||||
const file = Bun.file(filePath);
|
||||
if (!(await file.exists())) {
|
||||
set.status = 404;
|
||||
return { message: "Converted file not found." };
|
||||
}
|
||||
|
||||
return file;
|
||||
},
|
||||
{
|
||||
auth: true,
|
||||
|
||||
+140
-25
@@ -3,7 +3,7 @@ import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import db from "../db/db";
|
||||
import { Filename, Jobs } from "../db/types";
|
||||
import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, LANGUAGE, WEBROOT } from "../helpers/env";
|
||||
import { ALLOW_UNAUTHENTICATED, HIDE_HISTORY, LANGUAGE, TIMEZONE, WEBROOT } from "../helpers/env";
|
||||
import { userService } from "./user";
|
||||
import { EyeIcon } from "../icons/eye";
|
||||
import { DeleteIcon } from "../icons/delete";
|
||||
@@ -47,10 +47,27 @@ export const history = new Elysia().use(userService).get(
|
||||
`}
|
||||
>
|
||||
<article class="article">
|
||||
<h1 class="mb-4 text-xl">Results</h1>
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-xl">Results</h1>
|
||||
<div id="delete-selected-container">
|
||||
<button
|
||||
id="delete-selected-btn"
|
||||
class={`
|
||||
flex btn-secondary flex-row gap-2 text-contrast
|
||||
disabled:cursor-not-allowed disabled:opacity-50
|
||||
`}
|
||||
disabled
|
||||
>
|
||||
<DeleteIcon />{" "}
|
||||
<span>
|
||||
Delete Selected (<span id="selected-count">0</span>)
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
|
||||
w-full table-auto overflow-y-auto rounded-sm bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
@@ -59,7 +76,20 @@ export const history = new Elysia().use(userService).get(
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="select-all"
|
||||
class="size-4 cursor-pointer"
|
||||
title="Select all"
|
||||
/>
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
@@ -67,7 +97,7 @@ export const history = new Elysia().use(userService).get(
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
@@ -75,7 +105,7 @@ export const history = new Elysia().use(userService).get(
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
@@ -83,7 +113,7 @@ export const history = new Elysia().use(userService).get(
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
max-sm:hidden
|
||||
sm:px-4
|
||||
`}
|
||||
@@ -92,7 +122,7 @@ export const history = new Elysia().use(userService).get(
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
@@ -100,7 +130,7 @@ export const history = new Elysia().use(userService).get(
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
@@ -112,6 +142,14 @@ export const history = new Elysia().use(userService).get(
|
||||
{userJobs.map((job) => (
|
||||
<>
|
||||
<tr id={`job-row-${job.id}`}>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="size-4 cursor-pointer"
|
||||
data-checkbox-type="job"
|
||||
data-job-id={job.id}
|
||||
/>
|
||||
</td>
|
||||
<td class="job-details-toggle cursor-pointer" data-job-id={job.id}>
|
||||
<svg
|
||||
id={`arrow-${job.id}`}
|
||||
@@ -120,7 +158,7 @@ export const history = new Elysia().use(userService).get(
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="inline-block h-4 w-4"
|
||||
class="inline-block size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -129,7 +167,11 @@ export const history = new Elysia().use(userService).get(
|
||||
/>
|
||||
</svg>
|
||||
</td>
|
||||
<td safe>{new Date(job.date_created).toLocaleTimeString(LANGUAGE)}</td>
|
||||
<td safe>
|
||||
{new Date(job.date_created).toLocaleTimeString(LANGUAGE, {
|
||||
timeZone: TIMEZONE,
|
||||
})}
|
||||
</td>
|
||||
<td>{job.num_files}</td>
|
||||
<td class="max-sm:hidden">{job.finished_files}</td>
|
||||
<td safe>{job.status}</td>
|
||||
@@ -143,19 +185,21 @@ export const history = new Elysia().use(userService).get(
|
||||
>
|
||||
<EyeIcon />
|
||||
</a>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/delete/${job.id}`}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</a>
|
||||
<form action={`${WEBROOT}/delete/${job.id}`} method="POST" class="inline">
|
||||
<button
|
||||
type="submit"
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id={`details-${job.id}`} class="hidden">
|
||||
<td colspan="6">
|
||||
<td colspan="7">
|
||||
<div class="p-2 text-sm text-neutral-500">
|
||||
<div class="mb-1 font-semibold">Detailed File Information:</div>
|
||||
{job.files_detailed.map((file: Filename) => (
|
||||
@@ -167,7 +211,7 @@ export const history = new Elysia().use(userService).get(
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class={`mx-2 inline-block h-4 w-4 text-neutral-500`}
|
||||
class={`mx-2 inline-block size-4 text-neutral-500`}
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
@@ -192,26 +236,97 @@ export const history = new Elysia().use(userService).get(
|
||||
<script>
|
||||
{`
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Expand/collapse job details
|
||||
const toggles = document.querySelectorAll('.job-details-toggle');
|
||||
toggles.forEach(toggle => {
|
||||
toggle.addEventListener('click', function() {
|
||||
const jobId = this.dataset.jobId;
|
||||
const detailsRow = document.getElementById(\`details-\${jobId}\`);
|
||||
// The arrow SVG itself has the ID arrow-\${jobId}
|
||||
const arrow = document.getElementById(\`arrow-\${jobId}\`);
|
||||
|
||||
if (detailsRow && arrow) {
|
||||
detailsRow.classList.toggle("hidden");
|
||||
if (detailsRow.classList.contains("hidden")) {
|
||||
// Right-facing arrow (collapsed)
|
||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />';
|
||||
} else {
|
||||
// Down-facing arrow (expanded)
|
||||
arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Checkbox management
|
||||
const selectAllCheckbox = document.getElementById('select-all');
|
||||
const jobCheckboxes = document.querySelectorAll('[data-checkbox-type="job"]');
|
||||
const deleteSelectedBtn = document.getElementById('delete-selected-btn');
|
||||
const deleteSelectedContainer = document.getElementById('delete-selected-container');
|
||||
const selectedCountSpan = document.getElementById('selected-count');
|
||||
|
||||
function updateDeleteButton() {
|
||||
const checkedBoxes = Array.from(jobCheckboxes).filter(cb => cb.checked);
|
||||
if (checkedBoxes.length > 0) {
|
||||
deleteSelectedBtn.disabled = false;
|
||||
selectedCountSpan.textContent = checkedBoxes.length;
|
||||
} else {
|
||||
deleteSelectedBtn.disabled = true;
|
||||
selectedCountSpan.textContent = '0';
|
||||
}
|
||||
}
|
||||
|
||||
selectAllCheckbox?.addEventListener('change', function() {
|
||||
jobCheckboxes.forEach(checkbox => {
|
||||
checkbox.checked = this.checked;
|
||||
});
|
||||
updateDeleteButton();
|
||||
});
|
||||
|
||||
jobCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
const allChecked = Array.from(jobCheckboxes).every(cb => cb.checked);
|
||||
const someChecked = Array.from(jobCheckboxes).some(cb => cb.checked);
|
||||
if (selectAllCheckbox) {
|
||||
selectAllCheckbox.checked = allChecked;
|
||||
selectAllCheckbox.indeterminate = someChecked && !allChecked;
|
||||
}
|
||||
updateDeleteButton();
|
||||
});
|
||||
});
|
||||
|
||||
deleteSelectedBtn?.addEventListener('click', async function() {
|
||||
const checkedBoxes = Array.from(jobCheckboxes).filter(cb => cb.checked);
|
||||
const jobIds = checkedBoxes.map(cb => cb.dataset.jobId);
|
||||
|
||||
if (jobIds.length === 0) return;
|
||||
|
||||
const confirmed = confirm(\`Are you sure you want to delete \${jobIds.length} job(s)? This action cannot be undone.\`);
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('${WEBROOT}/delete-multiple', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ jobIds }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(\`HTTP error! status: \${response.status}\`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success || result.deleted > 0) {
|
||||
alert(\`Successfully deleted \${result.deleted} job(s).\${result.failed > 0 ? \` Failed to delete \${result.failed} job(s).\` : ''}\`);
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Failed to delete jobs. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting jobs:', error);
|
||||
alert('An error occurred while deleting jobs. Please try again.');
|
||||
}
|
||||
});
|
||||
});
|
||||
`}
|
||||
</script>
|
||||
|
||||
@@ -22,7 +22,7 @@ export const listConverters = new Elysia().use(userService).get(
|
||||
<h1 class="mb-4 text-xl">Converters</h1>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900 text-left
|
||||
w-full table-auto rounded-sm bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
[&_ul]:list-inside [&_ul]:list-disc
|
||||
|
||||
+58
-43
@@ -3,6 +3,7 @@ import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
import db from "../db/db";
|
||||
import { Filename, Jobs } from "../db/types";
|
||||
import { buildDownloadUrl } from "../helpers/buildDownloadUrl";
|
||||
import { ALLOW_UNAUTHENTICATED, WEBROOT } from "../helpers/env";
|
||||
import { DownloadIcon } from "../icons/download";
|
||||
import { DeleteIcon } from "../icons/delete";
|
||||
@@ -23,14 +24,16 @@ function ResultsArticle({
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<h1 class="text-xl">Results</h1>
|
||||
<div class="flex flex-row gap-4">
|
||||
<a
|
||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||
class="flex btn-secondary flex-row gap-2 text-contrast"
|
||||
href={`${WEBROOT}/delete/${job.id}`}
|
||||
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
|
||||
>
|
||||
<DeleteIcon /> <p>Delete</p>
|
||||
</a>
|
||||
<form action={`${WEBROOT}/delete/${job.id}`} method="POST">
|
||||
<button
|
||||
type="submit"
|
||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||
class="flex btn-secondary flex-row gap-2 text-contrast"
|
||||
{...(files.length !== job.num_files ? { disabled: true, "aria-busy": "true" } : "")}
|
||||
>
|
||||
<DeleteIcon /> <p>Delete</p>
|
||||
</button>
|
||||
</form>
|
||||
<a
|
||||
style={files.length !== job.num_files ? "pointer-events: none;" : ""}
|
||||
href={`${WEBROOT}/archive/${job.id}`}
|
||||
@@ -51,15 +54,15 @@ function ResultsArticle({
|
||||
class={`
|
||||
mb-4 inline-block h-2 w-full appearance-none overflow-hidden rounded-full border-0
|
||||
bg-neutral-700 bg-none text-accent-500 accent-accent-500
|
||||
[&::-moz-progress-bar]:bg-accent-500 [&::-webkit-progress-value]:rounded-full
|
||||
[&::-webkit-progress-value]:[background:none]
|
||||
[&::-moz-progress-bar]:bg-accent-500
|
||||
[&::-webkit-progress-value]:rounded-full [&::-webkit-progress-value]:[background:none]
|
||||
[&[value]::-webkit-progress-value]:bg-accent-500
|
||||
[&[value]::-webkit-progress-value]:transition-[inline-size]
|
||||
`}
|
||||
/>
|
||||
<table
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900 text-left
|
||||
w-full table-auto rounded-sm bg-neutral-900 text-left
|
||||
[&_td]:p-4
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
@@ -68,7 +71,7 @@ function ResultsArticle({
|
||||
<tr>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
@@ -76,7 +79,7 @@ function ResultsArticle({
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
@@ -84,7 +87,7 @@ function ResultsArticle({
|
||||
</th>
|
||||
<th
|
||||
class={`
|
||||
px-2 py-2
|
||||
p-2
|
||||
sm:px-4
|
||||
`}
|
||||
>
|
||||
@@ -93,35 +96,47 @@ function ResultsArticle({
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{files.map((file) => (
|
||||
<tr>
|
||||
<td safe class="max-w-[20vw] truncate">
|
||||
{file.output_file_name}
|
||||
</td>
|
||||
<td safe>{file.status}</td>
|
||||
<td class="flex flex-row gap-4">
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||
>
|
||||
<EyeIcon />
|
||||
</a>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={`${WEBROOT}/download/${outputPath}${file.output_file_name}`}
|
||||
download={file.output_file_name}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{files.map((file) => {
|
||||
const conversionFailed = ["Failed, check logs", "File type not supported"].includes(
|
||||
file.status,
|
||||
);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td safe class="max-w-[20vw] truncate">
|
||||
{file.output_file_name}
|
||||
</td>
|
||||
<td safe>{file.status}</td>
|
||||
<td class="flex flex-row gap-4">
|
||||
{conversionFailed ? (
|
||||
<span class="text-neutral-500">Unavailable</span>
|
||||
) : (
|
||||
<>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={buildDownloadUrl(WEBROOT, outputPath, file.output_file_name)}
|
||||
>
|
||||
<EyeIcon />
|
||||
</a>
|
||||
<a
|
||||
class={`
|
||||
text-accent-500 underline
|
||||
hover:text-accent-400
|
||||
`}
|
||||
href={buildDownloadUrl(WEBROOT, outputPath, file.output_file_name)}
|
||||
download={file.output_file_name}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</article>
|
||||
|
||||
+7
-6
@@ -126,8 +126,9 @@ export const root = new Elysia().use(userService).get(
|
||||
<table
|
||||
id="file-list"
|
||||
class={`
|
||||
w-full table-auto rounded bg-neutral-900
|
||||
[&_td]:p-4 [&_td]:first:max-w-[30vw] [&_td]:first:truncate
|
||||
w-full table-auto rounded-sm bg-neutral-900
|
||||
[&_td]:p-4
|
||||
[&_td]:first:max-w-[30vw] [&_td]:first:truncate
|
||||
[&_tr]:rounded-sm [&_tr]:border-b [&_tr]:border-neutral-800
|
||||
`}
|
||||
/>
|
||||
@@ -135,8 +136,8 @@ export const root = new Elysia().use(userService).get(
|
||||
<div
|
||||
id="dropzone"
|
||||
class={`
|
||||
relative flex h-48 w-full items-center justify-center rounded border border-dashed
|
||||
border-neutral-700 transition-all
|
||||
relative flex h-48 w-full items-center justify-center rounded-sm border
|
||||
border-dashed border-neutral-700 transition-all
|
||||
hover:border-neutral-600
|
||||
[&.dragover]:border-4 [&.dragover]:border-neutral-500
|
||||
`}
|
||||
@@ -170,7 +171,7 @@ export const root = new Elysia().use(userService).get(
|
||||
<article
|
||||
class={`
|
||||
convert_to_popup absolute z-2 m-0 hidden h-[30vh] max-h-[50vh] w-full flex-col
|
||||
overflow-x-hidden overflow-y-auto rounded bg-neutral-800
|
||||
overflow-x-hidden overflow-y-auto rounded-sm bg-neutral-800
|
||||
sm:h-[30vh]
|
||||
`}
|
||||
>
|
||||
@@ -190,7 +191,7 @@ export const root = new Elysia().use(userService).get(
|
||||
// https://stackoverflow.com/questions/121499/when-a-blur-event-occurs-how-can-i-find-out-which-element-focus-went-to#comment82388679_33325953
|
||||
tabindex={0}
|
||||
class={`
|
||||
target rounded bg-neutral-700 p-1 text-base
|
||||
target rounded-sm bg-neutral-700 p-1 text-base
|
||||
hover:bg-neutral-600
|
||||
`}
|
||||
data-value={`${target},${converter}`}
|
||||
|
||||
+4
-50
@@ -1,5 +1,3 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { jwt } from "@elysiajs/jwt";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { BaseHtml } from "../components/base";
|
||||
import { Header } from "../components/header";
|
||||
@@ -12,57 +10,12 @@ import {
|
||||
HTTP_ALLOWED,
|
||||
WEBROOT,
|
||||
} from "../helpers/env";
|
||||
import { userService } from "../services/user";
|
||||
|
||||
export { userService } from "../services/user";
|
||||
|
||||
export let FIRST_RUN = db.query("SELECT * FROM users").get() === null || false;
|
||||
|
||||
export const userService = new Elysia({ name: "user/service" })
|
||||
.use(
|
||||
jwt({
|
||||
name: "jwt",
|
||||
schema: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
secret: process.env.JWT_SECRET ?? randomUUID(),
|
||||
exp: "7d",
|
||||
}),
|
||||
)
|
||||
.model({
|
||||
signIn: t.Object({
|
||||
email: t.String(),
|
||||
password: t.String(),
|
||||
}),
|
||||
session: t.Cookie({
|
||||
auth: t.String(),
|
||||
jobId: t.Optional(t.String()),
|
||||
}),
|
||||
optionalSession: t.Cookie({
|
||||
auth: t.Optional(t.String()),
|
||||
jobId: t.Optional(t.String()),
|
||||
}),
|
||||
})
|
||||
.macro("auth", {
|
||||
cookie: "session",
|
||||
async resolve({ status, jwt, cookie: { auth } }) {
|
||||
if (!auth.value) {
|
||||
return status(401, {
|
||||
success: false,
|
||||
message: "Unauthorized",
|
||||
});
|
||||
}
|
||||
const user = await jwt.verify(auth.value);
|
||||
if (!user) {
|
||||
return status(401, {
|
||||
success: false,
|
||||
message: "Unauthorized",
|
||||
});
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
user,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export const user = new Elysia()
|
||||
.use(userService)
|
||||
.get("/setup", ({ redirect }) => {
|
||||
@@ -279,6 +232,7 @@ export const user = new Elysia()
|
||||
class="rounded-sm bg-neutral-800 p-3"
|
||||
placeholder="Email"
|
||||
autocomplete="email"
|
||||
autofocus
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { jwt } from "@elysiajs/jwt";
|
||||
import { Elysia, t } from "elysia";
|
||||
import { WEBROOT } from "../helpers/env";
|
||||
import { isHtmlPageRequest } from "../helpers/isHtmlPageRequest";
|
||||
|
||||
export const userService = new Elysia({ name: "user/service" })
|
||||
.use(
|
||||
jwt({
|
||||
name: "jwt",
|
||||
schema: t.Object({
|
||||
id: t.String(),
|
||||
}),
|
||||
secret: process.env.JWT_SECRET ?? randomUUID(),
|
||||
exp: "7d",
|
||||
}),
|
||||
)
|
||||
.model({
|
||||
signIn: t.Object({
|
||||
email: t.String(),
|
||||
password: t.String(),
|
||||
}),
|
||||
session: t.Cookie({
|
||||
auth: t.String(),
|
||||
jobId: t.Optional(t.String()),
|
||||
}),
|
||||
optionalSession: t.Cookie({
|
||||
auth: t.Optional(t.String()),
|
||||
jobId: t.Optional(t.String()),
|
||||
}),
|
||||
})
|
||||
.macro("auth", {
|
||||
cookie: "optionalSession",
|
||||
async resolve({ request, set, status, jwt, cookie: { auth } }) {
|
||||
const unauthorized = () => {
|
||||
if (isHtmlPageRequest(request)) {
|
||||
set.headers.location = `${WEBROOT}/login`;
|
||||
return status(302, {
|
||||
success: false,
|
||||
message: "Redirecting to login",
|
||||
});
|
||||
}
|
||||
|
||||
return status(401, {
|
||||
success: false,
|
||||
message: "Unauthorized",
|
||||
});
|
||||
};
|
||||
|
||||
if (!auth.value) {
|
||||
return unauthorized();
|
||||
}
|
||||
const user = await jwt.verify(auth.value);
|
||||
if (!user) {
|
||||
auth.remove();
|
||||
return unauthorized();
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
user,
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import fs from "fs";
|
||||
import { beforeEach, afterEach, expect, test, describe } from "bun:test";
|
||||
import { buildDaselArgs, convert } from "../../src/converters/dasel";
|
||||
import type { ExecFileFn } from "../../src/converters/types";
|
||||
|
||||
const originalWriteFile = fs.writeFile;
|
||||
|
||||
describe("convert", () => {
|
||||
let mockExecFile: ExecFileFn;
|
||||
|
||||
beforeEach(() => {
|
||||
// mock fs.writeFile
|
||||
// @ts-expect-error: property __promisify__ is missing
|
||||
fs.writeFile = (path, data, cb) => cb(null);
|
||||
// mock execFile
|
||||
mockExecFile = (cmd, args, callback) => callback(null, "output-data", "");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// reset fs.writeFile
|
||||
fs.writeFile = originalWriteFile;
|
||||
});
|
||||
|
||||
test("should build dasel v3 arguments", () => {
|
||||
expect(buildDaselArgs("input.yaml", "yaml", "json")).toEqual([
|
||||
"--var",
|
||||
"data=yaml:file:input.yaml",
|
||||
"--out",
|
||||
"json",
|
||||
"$data",
|
||||
]);
|
||||
});
|
||||
|
||||
test("should call dasel with correct arguments and write output", async () => {
|
||||
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
|
||||
mockExecFile = (cmd, args, callback) => {
|
||||
calledArgs = [cmd, args, callback];
|
||||
callback(null, "output-data", "");
|
||||
};
|
||||
|
||||
let writeFileCalled = false;
|
||||
// @ts-expect-error: property __promisify__ is missing
|
||||
fs.writeFile = (path, data, cb) => {
|
||||
writeFileCalled = true;
|
||||
expect(path).toBe("output.json");
|
||||
expect(data).toBe("output-data");
|
||||
// @ts-expect-error: could not be callable with null
|
||||
cb(null);
|
||||
};
|
||||
|
||||
const result = await convert(
|
||||
"input.yaml",
|
||||
"yaml",
|
||||
"json",
|
||||
"output.json",
|
||||
undefined,
|
||||
mockExecFile,
|
||||
);
|
||||
|
||||
expect(calledArgs[0]).toBe("dasel");
|
||||
expect(calledArgs[1]).toEqual(["--var", "data=yaml:file:input.yaml", "--out", "json", "$data"]);
|
||||
expect(writeFileCalled).toBe(true);
|
||||
expect(result).toBe("Done");
|
||||
});
|
||||
|
||||
test("should close dasel stdin so v3 does not wait for input", async () => {
|
||||
let stdinEnded = false;
|
||||
mockExecFile = (cmd, args, callback) => {
|
||||
callback(null, "output-data", "");
|
||||
return { stdin: { end: () => (stdinEnded = true) } } as ReturnType<ExecFileFn>;
|
||||
};
|
||||
|
||||
await convert("input.yaml", "yaml", "json", "output.json", undefined, mockExecFile);
|
||||
|
||||
expect(stdinEnded).toBe(true);
|
||||
});
|
||||
|
||||
test("should reject if execFile returns an error", async () => {
|
||||
mockExecFile = (cmd, args, callback) => callback(new Error("fail"), "", "");
|
||||
await expect(
|
||||
convert("input.yaml", "yaml", "json", "output.json", undefined, mockExecFile),
|
||||
).rejects.toMatch(/error: Error: fail/);
|
||||
});
|
||||
|
||||
test("should reject if writeFile fails", async () => {
|
||||
// @ts-expect-error: property __promisify__ is missing
|
||||
fs.writeFile = (path, data, cb) => cb(new Error("write fail"));
|
||||
await expect(
|
||||
convert("input.yaml", "yaml", "json", "output.json", undefined, (cmd, args, cb) =>
|
||||
cb(null, "output-data", ""),
|
||||
),
|
||||
).rejects.toMatch(/Failed to write output/);
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,18 @@
|
||||
import { beforeEach, expect, test } from "bun:test";
|
||||
import { convert } from "../../src/converters/ffmpeg";
|
||||
import type { ExecFileOptions } from "node:child_process";
|
||||
|
||||
let calls: string[][] = [];
|
||||
let lastOptions: ExecFileOptions | undefined;
|
||||
|
||||
function mockExecFile(
|
||||
_cmd: string,
|
||||
args: string[],
|
||||
options: ExecFileOptions,
|
||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||
) {
|
||||
calls.push(args);
|
||||
lastOptions = options;
|
||||
if (args.includes("fail.mov")) {
|
||||
callback(new Error("mock failure"), "", "Fake stderr: fail");
|
||||
} else {
|
||||
@@ -18,6 +22,7 @@ function mockExecFile(
|
||||
|
||||
beforeEach(() => {
|
||||
calls = [];
|
||||
lastOptions = undefined;
|
||||
delete process.env.FFMPEG_ARGS;
|
||||
});
|
||||
|
||||
@@ -168,6 +173,7 @@ test("logs stderr when execFile returns only stderr and no error", async () => {
|
||||
const mockExecFileStderrOnly = (
|
||||
_cmd: string,
|
||||
_args: string[],
|
||||
_options: ExecFileOptions,
|
||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||
) => {
|
||||
callback(null, "", "Only stderr output");
|
||||
@@ -179,3 +185,13 @@ test("logs stderr when execFile returns only stderr and no error", async () => {
|
||||
|
||||
expect(loggedMessage).toBe("stderr: Only stderr output");
|
||||
});
|
||||
|
||||
test("passes a maxBuffer above the 1 MB default so long conversions don't overflow stderr (#565)", async () => {
|
||||
await convert("in.mkv", "mkv", "h264.mp4", "out.mp4", undefined, mockExecFile);
|
||||
|
||||
// execFile's default maxBuffer is 1 MB; ffmpeg's progress output on a long
|
||||
// encode exceeds it and the conversion fails with "stderr maxBuffer length
|
||||
// exceeded". Lock in the raised buffer (must match FFMPEG_MAX_BUFFER in
|
||||
// ffmpeg.ts) so a regression to a smaller-but-still-over-1-MB value is caught.
|
||||
expect(lastOptions?.maxBuffer).toBe(1024 * 1024 * 64);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
import { test } from "bun:test";
|
||||
import { beforeEach, expect, test } from "bun:test";
|
||||
import type { ExecFileException } from "node:child_process";
|
||||
import { convert } from "../../src/converters/graphicsmagick";
|
||||
import { ExecFileFn } from "../../src/converters/types";
|
||||
import { runCommonTests } from "./helpers/commonTests";
|
||||
|
||||
let calls: string[][] = [];
|
||||
|
||||
beforeEach(() => {
|
||||
calls = [];
|
||||
});
|
||||
|
||||
runCommonTests(convert);
|
||||
|
||||
test.skip("dummy - required to trigger test detection", () => {});
|
||||
test("convert applies EXIF auto-orient", async () => {
|
||||
let command = "";
|
||||
const mockExecFile: ExecFileFn = (
|
||||
cmd: string,
|
||||
args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
) => {
|
||||
command = cmd;
|
||||
calls.push(args);
|
||||
callback(null, "", "");
|
||||
};
|
||||
|
||||
const result = await convert("input.jpg", "jpg", "pdf", "output.pdf", undefined, mockExecFile);
|
||||
|
||||
expect(result).toBe("Done");
|
||||
expect(command).toBe("gm");
|
||||
expect(calls[0]).toEqual(["convert", "input.jpg", "-auto-orient", "output.pdf"]);
|
||||
});
|
||||
|
||||
@@ -163,3 +163,19 @@ test("convert respects emf as input filetype", async () => {
|
||||
);
|
||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||
});
|
||||
|
||||
test("convert applies EXIF auto-orient", async () => {
|
||||
const mockExecFile: ExecFileFn = (
|
||||
_cmd: string,
|
||||
_args: string[],
|
||||
callback: (err: ExecFileException | null, stdout: string, stderr: string) => void,
|
||||
) => {
|
||||
calls.push(_args);
|
||||
callback(null, "", "");
|
||||
};
|
||||
|
||||
const result = await convert("input.jpg", "jpg", "png", "output.png", undefined, mockExecFile);
|
||||
|
||||
expect(result).toBe("Done");
|
||||
expect(calls[0]).toEqual(expect.arrayContaining(["input.jpg", "-auto-orient", "output.png"]));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
import { afterEach, beforeEach, expect, test } from "bun:test";
|
||||
import { convert } from "../../src/converters/libreoffice";
|
||||
import type { ExecFileFn } from "../../src/converters/types";
|
||||
import { filters, getFilters } from "../../src/converters/libreoffice";
|
||||
|
||||
function requireDefined<T>(value: T, msg: string): NonNullable<T> {
|
||||
if (value === undefined || value === null) throw new Error(msg);
|
||||
return value as NonNullable<T>;
|
||||
}
|
||||
|
||||
// --- capture/inspect execFile calls -----------------------------------------
|
||||
type Call = { cmd: string; args: string[] };
|
||||
let calls: Call[] = [];
|
||||
|
||||
let behavior:
|
||||
| { kind: "success"; stdout?: string; stderr?: string }
|
||||
| { kind: "error"; message?: string; stderr?: string } = { kind: "success" };
|
||||
|
||||
const mockExecFile: ExecFileFn = (cmd, args, cb) => {
|
||||
calls.push({ cmd, args });
|
||||
if (behavior.kind === "error") {
|
||||
cb(new Error(behavior.message ?? "mock failure"), "", behavior.stderr ?? "");
|
||||
} else {
|
||||
cb(null, behavior.stdout ?? "ok", behavior.stderr ?? "");
|
||||
}
|
||||
// We don't return a real ChildProcess in tests.
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// --- capture console output (no terminal noise) ------------------------------
|
||||
let logs: string[] = [];
|
||||
let errors: string[] = [];
|
||||
|
||||
const originalLog = console.log;
|
||||
const originalError = console.error;
|
||||
|
||||
// Use Console["log"] for typing; avoids explicit `any`
|
||||
const makeSink =
|
||||
(sink: string[]): Console["log"] =>
|
||||
(...data) => {
|
||||
sink.push(data.map(String).join(" "));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
calls = [];
|
||||
behavior = { kind: "success" };
|
||||
|
||||
logs = [];
|
||||
errors = [];
|
||||
console.log = makeSink(logs);
|
||||
console.error = makeSink(errors);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
console.log = originalLog;
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
// --- core behavior -----------------------------------------------------------
|
||||
test("invokes soffice with --headless and outdir derived from targetPath", async () => {
|
||||
await convert("in.docx", "docx", "odt", "out/out.odt", undefined, mockExecFile);
|
||||
|
||||
const { cmd, args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||
expect(cmd).toBe("soffice");
|
||||
expect(args).toEqual([
|
||||
"--headless",
|
||||
"--infilter=MS Word 2007 XML",
|
||||
"--convert-to",
|
||||
"odt:writer8",
|
||||
"--outdir",
|
||||
"out",
|
||||
"in.docx",
|
||||
]);
|
||||
});
|
||||
|
||||
test("uses only outFilter when input has no filter (e.g., pdf -> txt)", async () => {
|
||||
await convert("in.pdf", "pdf", "txt", "out/out.txt", undefined, mockExecFile);
|
||||
|
||||
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||
|
||||
expect(args).toEqual([
|
||||
"--headless",
|
||||
"--infilter=writer_pdf_import",
|
||||
"--convert-to",
|
||||
"txt:Text",
|
||||
"--outdir",
|
||||
"out",
|
||||
"in.pdf",
|
||||
]);
|
||||
});
|
||||
|
||||
test("uses only infilter when convertTo has no out filter (e.g., docx -> pdf)", async () => {
|
||||
await convert("in.docx", "docx", "pdf", "out/out.pdf", undefined, mockExecFile);
|
||||
|
||||
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||
|
||||
// If docx has an infilter, it should be present
|
||||
expect(args).toEqual(["--headless", "--convert-to", "pdf", "--outdir", "out", "in.docx"]);
|
||||
|
||||
const i = args.indexOf("--convert-to");
|
||||
expect(i).toBeGreaterThanOrEqual(0);
|
||||
expect(args[i + 1]).toBe("pdf");
|
||||
expect(args.slice(-2)).toEqual(["out", "in.docx"]);
|
||||
});
|
||||
|
||||
test("does not force an infilter for wps (Microsoft Works, not MS Word 97)", async () => {
|
||||
// Regression test for https://github.com/C4illin/ConvertX/issues/582 -
|
||||
// forcing --infilter="MS Word 97" on a genuine .wps file makes soffice
|
||||
// reject it with "source file could not be loaded". No forced infilter
|
||||
// lets LibreOffice auto-detect the real format instead.
|
||||
await convert("in.wps", "wps", "docx", "out/out.docx", undefined, mockExecFile);
|
||||
|
||||
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||
|
||||
expect(args).toEqual([
|
||||
"--headless",
|
||||
"--convert-to",
|
||||
"docx:MS Word 2007 XML",
|
||||
"--outdir",
|
||||
"out",
|
||||
"in.wps",
|
||||
]);
|
||||
expect(args.some((a) => a.startsWith("--infilter"))).toBe(false);
|
||||
});
|
||||
|
||||
test("does not force an outfilter for wps as an export target either", async () => {
|
||||
// wps shares one filter-map entry for both directions (see the comment in
|
||||
// libreoffice.ts) - docx's own infilter is still emitted (that describes
|
||||
// the real source file), but no --convert-to wps:<filter> suffix is
|
||||
// forced. Verified against a real soffice: this exact invocation succeeds,
|
||||
// with LibreOffice's default export filter for .wps ("MS Word 97" - the
|
||||
// same filter the map pinned before this change, so output is unchanged).
|
||||
// LibreOffice has no Works export filter, so no suffix could do better.
|
||||
await convert("in.docx", "docx", "wps", "out/out.wps", undefined, mockExecFile);
|
||||
|
||||
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||
|
||||
expect(args).toEqual([
|
||||
"--headless",
|
||||
"--infilter=MS Word 2007 XML",
|
||||
"--convert-to",
|
||||
"wps",
|
||||
"--outdir",
|
||||
"out",
|
||||
"in.docx",
|
||||
]);
|
||||
});
|
||||
|
||||
test("strips leading './' from outdir", async () => {
|
||||
await convert("in.txt", "txt", "docx", "./out/out.docx", undefined, mockExecFile);
|
||||
|
||||
const { args } = requireDefined(calls[0], "Expected at least one execFile call");
|
||||
|
||||
const outDirIdx = args.indexOf("--outdir");
|
||||
expect(outDirIdx).toBeGreaterThanOrEqual(0);
|
||||
expect(args[outDirIdx + 1]).toBe("out");
|
||||
});
|
||||
|
||||
// --- promise settlement ------------------------------------------------------
|
||||
test("resolves with 'Done' when execFile succeeds", async () => {
|
||||
behavior = { kind: "success", stdout: "fine", stderr: "" };
|
||||
await expect(
|
||||
convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile),
|
||||
).resolves.toBe("Done");
|
||||
});
|
||||
|
||||
test("rejects when execFile returns an error", async () => {
|
||||
behavior = { kind: "error", message: "convert failed", stderr: "oops" };
|
||||
await expect(
|
||||
convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile),
|
||||
).rejects.toMatch(/error: Error: convert failed/);
|
||||
});
|
||||
|
||||
// --- logging behavior --------------------------------------------------------
|
||||
test("logs stdout when present", async () => {
|
||||
behavior = { kind: "success", stdout: "hello", stderr: "" };
|
||||
|
||||
await convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile);
|
||||
|
||||
expect(logs).toContain("stdout: hello");
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("logs stderr when present", async () => {
|
||||
behavior = { kind: "success", stdout: "", stderr: "uh-oh" };
|
||||
|
||||
await convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile);
|
||||
|
||||
expect(errors).toContain("stderr: uh-oh");
|
||||
// When stdout is empty, no stdout log
|
||||
expect(logs.find((l) => l.startsWith("stdout:"))).toBeUndefined();
|
||||
});
|
||||
|
||||
test("logs both stdout and stderr when both are present", async () => {
|
||||
behavior = { kind: "success", stdout: "alpha", stderr: "beta" };
|
||||
|
||||
await convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile);
|
||||
|
||||
expect(logs).toContain("stdout: alpha");
|
||||
expect(errors).toContain("stderr: beta");
|
||||
});
|
||||
|
||||
test("logs stderr on exec error as well", async () => {
|
||||
behavior = { kind: "error", message: "boom", stderr: "EPIPE" };
|
||||
|
||||
expect(convert("in.txt", "txt", "docx", "out/out.docx", undefined, mockExecFile)).rejects.toMatch(
|
||||
/error: Error: boom/,
|
||||
);
|
||||
|
||||
// The callback still provided stderr; your implementation logs it before settling
|
||||
expect(errors).toContain("stderr: EPIPE");
|
||||
});
|
||||
|
||||
// --- calc filter branch (test-only exports) ---------------------------------
|
||||
test("getFilters returns calc mapping when present", () => {
|
||||
// temporarily add entries to calc mapping
|
||||
filters.calc["testfoo"] = "TestFooFilter";
|
||||
filters.calc["testbar"] = "TestBarFilter";
|
||||
|
||||
try {
|
||||
const res = getFilters("testfoo", "testbar");
|
||||
expect(res).toEqual(["TestFooFilter", "TestBarFilter"]);
|
||||
} finally {
|
||||
// cleanup
|
||||
delete filters.calc["testfoo"];
|
||||
delete filters.calc["testbar"];
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,393 @@
|
||||
// Set isolated DB path before import to protect production data
|
||||
import { tmpdir } from "node:os";
|
||||
import { mkdirSync, rmSync, existsSync } from "node:fs";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
|
||||
const testDbDir = join(tmpdir(), "converter-test-db");
|
||||
rmSync(testDbDir, { recursive: true, force: true });
|
||||
mkdirSync(testDbDir, { recursive: true });
|
||||
process.env.DB_PATH = join(testDbDir, "test.sqlite");
|
||||
|
||||
import { test, expect, afterAll, afterEach } from "bun:test";
|
||||
import type { Cookie } from "elysia";
|
||||
import { writeFile, mkdir, rm, readFile } from "fs/promises";
|
||||
import { Database } from "bun:sqlite";
|
||||
|
||||
// dynamic import ensures that the module is loaded after the environment variable is set
|
||||
const converterModule = await import("../../src/converters/main");
|
||||
const { getPossibleTargets, getAllTargets, getAllInputs, handleConvert, mainConverter, chunks } =
|
||||
converterModule;
|
||||
|
||||
// Isolated test database: avoids mutation of ./data/mydb.sqlite
|
||||
const dbPath = process.env.DB_PATH ?? join(testDbDir, "test.sqlite");
|
||||
const dbDir = dirname(resolve(dbPath));
|
||||
|
||||
if (!existsSync(dbDir)) {
|
||||
mkdirSync(dbDir, { recursive: true });
|
||||
}
|
||||
|
||||
const testDb = new Database(dbPath, { create: true });
|
||||
|
||||
// cleans up the table before each test for real isolation (even with parallel tests)
|
||||
afterEach(() => {
|
||||
try {
|
||||
testDb.query("DELETE FROM file_names");
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
// ignore only the expected error for missing tables; rethrow real issues
|
||||
if (!err.message.includes("no such table")) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// closes the DB and removes the temp directory after all tests finish
|
||||
afterAll(() => {
|
||||
testDb.close();
|
||||
rmSync(testDbDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
// Mock factory for jobId Cookie to avoid repeated `as Cookie` casts
|
||||
function createMockJobId(value: string): Cookie<string | undefined> {
|
||||
return { value } as Cookie<string | undefined>;
|
||||
}
|
||||
|
||||
test("getPossibleTargets, getAllTargets and getAllInputs include vcf/csv mapping", () => {
|
||||
const possible = getPossibleTargets("vcf");
|
||||
// should have an entry for the vcf converter
|
||||
expect(Object.keys(possible).length).toBeGreaterThan(0);
|
||||
// getAllTargets should include 'vcf' converter target csv
|
||||
const allTargets = getAllTargets();
|
||||
// Be defensive: allTargets.vcf may be undefined in some builds
|
||||
expect(allTargets).toHaveProperty("vcf");
|
||||
expect(Array.isArray(allTargets.vcf)).toBe(true);
|
||||
expect((allTargets.vcf ?? []).includes("csv")).toBe(true);
|
||||
|
||||
const allInputs = getAllInputs("vcf");
|
||||
expect(allInputs.includes("vcf")).toBe(true);
|
||||
});
|
||||
|
||||
test("handleConvert uses vcf converter to transform .vcf to .csv and records DB entry", async () => {
|
||||
const uploadsDir = "./data/uploads/test-main/";
|
||||
const outputDir = "./data/output/test-main/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const fileName = "contact.vcf";
|
||||
const inputPath = `${uploadsDir}${fileName}`;
|
||||
const sampleVcf = `BEGIN:VCARD
|
||||
FN:John Doe
|
||||
N:Doe;John;;;
|
||||
TEL;TYPE=CELL:123456789
|
||||
EMAIL:john@example.com
|
||||
ORG:Example Inc;
|
||||
END:VCARD
|
||||
`;
|
||||
await writeFile(inputPath, sampleVcf, "utf-8");
|
||||
|
||||
const jobId = createMockJobId("4242");
|
||||
|
||||
await handleConvert([fileName], uploadsDir, outputDir, "csv", "vcf", jobId);
|
||||
|
||||
const outPath = `${outputDir}contact.csv`;
|
||||
const out = await readFile(outPath, "utf-8");
|
||||
|
||||
// CSV should contain headers and the name
|
||||
expect(out.includes("Full Name")).toBe(true);
|
||||
expect(out.includes("John Doe")).toBe(true);
|
||||
|
||||
// cleanup
|
||||
await rm(inputPath);
|
||||
await rm(outPath);
|
||||
});
|
||||
|
||||
test("handleConvert with unsupported format does not throw", async () => {
|
||||
const uploadsDir = "./data/uploads/test-unsupported/";
|
||||
const outputDir = "./data/output/test-unsupported/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
// Create a dummy file with unsupported extension
|
||||
const fileName = "dummy.xyz123";
|
||||
const inputPath = `${uploadsDir}${fileName}`;
|
||||
await writeFile(inputPath, "dummy content", "utf-8");
|
||||
|
||||
// Try to convert unsupported format
|
||||
const jobId = createMockJobId("unsupported-test");
|
||||
// This should not throw, just log that no converter is available
|
||||
await expect(
|
||||
handleConvert([fileName], uploadsDir, outputDir, "pdf", "xyz123", jobId),
|
||||
).resolves.toBeUndefined();
|
||||
|
||||
await rm(inputPath);
|
||||
});
|
||||
|
||||
test("handleConvert with multiple files processes them", async () => {
|
||||
const uploadsDir = "./data/uploads/test-multi/";
|
||||
const outputDir = "./data/output/test-multi/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
// Create multiple vcf files
|
||||
const files = ["contact1.vcf", "contact2.vcf", "contact3.vcf"];
|
||||
const baseVcf = `BEGIN:VCARD
|
||||
FN:Test Contact
|
||||
N:Contact;Test;;;
|
||||
END:VCARD
|
||||
`;
|
||||
|
||||
for (const fileName of files) {
|
||||
await writeFile(`${uploadsDir}${fileName}`, baseVcf, "utf-8");
|
||||
}
|
||||
|
||||
const jobId = createMockJobId("multi-test");
|
||||
await handleConvert(files, uploadsDir, outputDir, "csv", "vcf", jobId);
|
||||
|
||||
// Verify all output files were created
|
||||
for (const fileName of files) {
|
||||
const outputFileName = fileName.replace(".vcf", ".csv");
|
||||
const outPath = `${outputDir}${outputFileName}`;
|
||||
expect(await readFile(outPath, "utf-8")).toBeTruthy();
|
||||
await rm(outPath);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
for (const fileName of files) {
|
||||
await rm(`${uploadsDir}${fileName}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("handleConvert with explicit converter skips discovery", async () => {
|
||||
const uploadsDir = "./data/uploads/test-explicit/";
|
||||
const outputDir = "./data/output/test-explicit/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const fileName = "test.vcf";
|
||||
const inputPath = `${uploadsDir}${fileName}`;
|
||||
const sampleVcf = `BEGIN:VCARD
|
||||
FN:Explicit Test
|
||||
N:Test;Explicit;;;
|
||||
END:VCARD
|
||||
`;
|
||||
await writeFile(inputPath, sampleVcf, "utf-8");
|
||||
|
||||
// Use explicit vcf converter (avoids discovery loop)
|
||||
const jobId = createMockJobId("explicit-test");
|
||||
await handleConvert([fileName], uploadsDir, outputDir, "csv", "vcf", jobId);
|
||||
|
||||
const outPath = `${outputDir}test.csv`;
|
||||
expect(await readFile(outPath, "utf-8")).toBeTruthy();
|
||||
|
||||
await rm(inputPath);
|
||||
await rm(outPath);
|
||||
});
|
||||
|
||||
test("handleConvert with dvisvgm discovers converter from category keys", async () => {
|
||||
const uploadsDir = "./data/uploads/test-dvisvgm/";
|
||||
const outputDir = "./data/output/test-dvisvgm/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
// Create a simple DVI-like file (dvisvgm would normally handle .dvi files)
|
||||
// For testing we'll use a latex file and ask for svg output, which should fail gracefully
|
||||
const fileName = "test.tex";
|
||||
const inputPath = `${uploadsDir}${fileName}`;
|
||||
await writeFile(
|
||||
inputPath,
|
||||
"\\documentclass{article}\\begin{document}test\\end{document}",
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
// This tests that converter discovery iterates through all converters
|
||||
// and tries to find one matching tex -> svg
|
||||
const jobId = createMockJobId("dvi-test");
|
||||
await handleConvert([fileName], uploadsDir, outputDir, "svg", "tex", jobId);
|
||||
|
||||
await rm(inputPath);
|
||||
});
|
||||
|
||||
test("handleConvert processes multiple files with vcf converter across categories", async () => {
|
||||
const uploadsDir = "./data/uploads/test-vcf-multi/";
|
||||
const outputDir = "./data/output/test-vcf-multi/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const baseVcf = `BEGIN:VCARD
|
||||
FN:Multi Test
|
||||
N:Test;Multi;;;
|
||||
TEL:9999
|
||||
EMAIL:multi@test.com
|
||||
END:VCARD
|
||||
`;
|
||||
|
||||
// Create 2 vcf files to test looping through fileNames
|
||||
const files = ["a.vcf", "b.vcf"];
|
||||
for (const f of files) {
|
||||
await writeFile(`${uploadsDir}${f}`, baseVcf, "utf-8");
|
||||
}
|
||||
|
||||
// Explicit converter to hit the properties access code path
|
||||
const jobId = createMockJobId("vcf-multi-test");
|
||||
await handleConvert(files, uploadsDir, outputDir, "csv", "vcf", jobId);
|
||||
|
||||
for (const f of files) {
|
||||
const csvName = f.replace(".vcf", ".csv");
|
||||
expect(await readFile(`${outputDir}${csvName}`, "utf-8")).toBeTruthy();
|
||||
await rm(`${outputDir}${csvName}`);
|
||||
}
|
||||
|
||||
for (const f of files) {
|
||||
await rm(`${uploadsDir}${f}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("chunks with size 0 returns entire array as single chunk", () => {
|
||||
const arr = [1, 2, 3, 4, 5];
|
||||
const result = chunks(arr, 0);
|
||||
expect(result).toEqual([[1, 2, 3, 4, 5]]);
|
||||
});
|
||||
|
||||
test("chunks with negative size returns entire array as single chunk", () => {
|
||||
const arr = ["a", "b", "c"];
|
||||
const result = chunks(arr, -1);
|
||||
expect(result).toEqual([["a", "b", "c"]]);
|
||||
});
|
||||
|
||||
test("chunks with size larger than array returns single chunk", () => {
|
||||
const arr = [1, 2];
|
||||
const result = chunks(arr, 10);
|
||||
expect(result).toEqual([[1, 2]]);
|
||||
});
|
||||
|
||||
test("chunks with exact division returns equal-sized chunks", () => {
|
||||
const arr = [1, 2, 3, 4, 5, 6];
|
||||
const result = chunks(arr, 2);
|
||||
expect(result).toEqual([
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
[5, 6],
|
||||
]);
|
||||
});
|
||||
|
||||
test("mainConverter returns 'File type not supported' for unsupported combination", async () => {
|
||||
const result = await mainConverter("test.xyz", "xyz", "abc", "out.abc");
|
||||
expect(result).toBe("File type not supported");
|
||||
});
|
||||
|
||||
test("mainConverter auto-discovers converter when not specified", async () => {
|
||||
const uploadsDir = "./data/uploads/test-discover/";
|
||||
const outputDir = "./data/output/test-discover/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const fileName = "test.vcf";
|
||||
const inputPath = `${uploadsDir}${fileName}`;
|
||||
const outPath = `${outputDir}test.csv`;
|
||||
const sampleVcf = `BEGIN:VCARD
|
||||
FN:Discover Test
|
||||
N:Test;Discover;;;
|
||||
END:VCARD
|
||||
`;
|
||||
await writeFile(inputPath, sampleVcf, "utf-8");
|
||||
|
||||
// Call mainConverter without explicit converterName to trigger discovery
|
||||
const result = await mainConverter(inputPath, "vcf", "csv", outPath, undefined, undefined);
|
||||
expect(result).toBe("Done");
|
||||
expect(await readFile(outPath, "utf-8")).toBeTruthy();
|
||||
|
||||
await rm(inputPath);
|
||||
await rm(outPath);
|
||||
});
|
||||
|
||||
test("mainConverter returns 'Failed, check logs' when converter throws", async () => {
|
||||
// Try with a file that might cause issues (non-existent input)
|
||||
// This should trigger the catch block
|
||||
const result = await mainConverter(
|
||||
"/nonexistent/path.vcf",
|
||||
"vcf",
|
||||
"csv",
|
||||
"out.csv",
|
||||
undefined,
|
||||
"vcf",
|
||||
);
|
||||
expect(result).toBe("Failed, check logs");
|
||||
});
|
||||
|
||||
test("handleConvert with normalization covers fileTypeOrig variations", async () => {
|
||||
const uploadsDir = "./data/uploads/test-normalize/";
|
||||
const outputDir = "./data/output/test-normalize/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
// Use a file extension that gets normalized (e.g., .htm -> .html)
|
||||
const fileName = "index.htm";
|
||||
const inputPath = `${uploadsDir}${fileName}`;
|
||||
const sampleHtml = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>Test</title></head>
|
||||
<body>Test Content</body>
|
||||
</html>`;
|
||||
await writeFile(inputPath, sampleHtml, "utf-8");
|
||||
|
||||
// htm normalizes to html; libreoffice can handle html -> pdf
|
||||
const jobId = createMockJobId("normalize-test");
|
||||
await handleConvert([fileName], uploadsDir, outputDir, "pdf", "htm", jobId);
|
||||
|
||||
await rm(inputPath);
|
||||
// PDF output may or may not exist depending on soffice availability, so we don't check it
|
||||
});
|
||||
|
||||
test("handleConvert with explicit converter processes VCF to CSV", async () => {
|
||||
const uploadsDir = "./data/uploads/test-main-extra/";
|
||||
const outputDir = "./data/output/test-main-extra/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const fileName = "contact.vcf";
|
||||
const inputPath = `${uploadsDir}${fileName}`;
|
||||
const outPath = `${outputDir}contact.csv`;
|
||||
const sampleVcf = `BEGIN:VCARD
|
||||
FN:Jane Roe
|
||||
N:Roe;Jane;;;
|
||||
TEL;TYPE=CELL:555
|
||||
EMAIL:jane@example.com
|
||||
END:VCARD
|
||||
`;
|
||||
await writeFile(inputPath, sampleVcf, "utf-8");
|
||||
|
||||
// Call handleConvert with an explicit converter name ("vcf") to transform VCF to CSV
|
||||
const jobId = createMockJobId("discovery-test");
|
||||
await handleConvert([fileName], uploadsDir, outputDir, "csv", "vcf", jobId);
|
||||
|
||||
const out = await readFile(outPath, "utf-8");
|
||||
expect(out.includes("Jane Roe")).toBe(true);
|
||||
|
||||
await rm(inputPath);
|
||||
await rm(outPath);
|
||||
});
|
||||
|
||||
test("handleConvert handles files without extension by appending output extension", async () => {
|
||||
const uploadsDir = "./data/uploads/test-main-noext/";
|
||||
const outputDir = "./data/output/test-main-noext/";
|
||||
await mkdir(uploadsDir, { recursive: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
|
||||
const fileName = "noextfile"; // no extension
|
||||
const inputPath = `${uploadsDir}${fileName}`;
|
||||
const sampleVcf = `BEGIN:VCARD
|
||||
FN:No Ext
|
||||
N:Ext;No;;;
|
||||
END:VCARD
|
||||
`;
|
||||
await writeFile(inputPath, sampleVcf, "utf-8");
|
||||
|
||||
const outPath = `${outputDir}${fileName}.csv`;
|
||||
// Call handleConvert with explicit vcf converter (no extension on input)
|
||||
const jobId = createMockJobId("noext-test");
|
||||
await handleConvert([fileName], uploadsDir, outputDir, "csv", "vcf", jobId);
|
||||
|
||||
await rm(inputPath);
|
||||
await rm(outPath);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import { test } from "bun:test";
|
||||
import { convert } from "../../src/converters/markitdown";
|
||||
import { runCommonTests } from "./helpers/commonTests";
|
||||
|
||||
runCommonTests(convert);
|
||||
|
||||
test.skip("dummy - required to trigger test detection", () => {});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { beforeEach, expect, test, describe } from "bun:test";
|
||||
import { convert } from "../../src/converters/pandoc";
|
||||
import type { ExecFileFn } from "../../src/converters/types";
|
||||
|
||||
describe("convert", () => {
|
||||
let mockExecFile: ExecFileFn;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecFile = (cmd, args, callback) => callback(null, "output-data", "");
|
||||
});
|
||||
|
||||
test("should call pandoc with correct arguments (normal)", async () => {
|
||||
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
|
||||
mockExecFile = (cmd, args, callback) => {
|
||||
calledArgs = [cmd, args, callback];
|
||||
callback(null, "output-data", "");
|
||||
};
|
||||
|
||||
const result = await convert(
|
||||
"input.md",
|
||||
"markdown",
|
||||
"html",
|
||||
"output.html",
|
||||
undefined,
|
||||
mockExecFile,
|
||||
);
|
||||
|
||||
expect(calledArgs[0]).toBe("pandoc");
|
||||
expect(calledArgs[1]).toEqual([
|
||||
"input.md",
|
||||
"-f",
|
||||
"markdown",
|
||||
"-t",
|
||||
"html",
|
||||
"-o",
|
||||
"output.html",
|
||||
]);
|
||||
expect(result).toBe("Done");
|
||||
});
|
||||
|
||||
test("should add xelatex argument for pdf/latex", async () => {
|
||||
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
|
||||
mockExecFile = (cmd, args, callback) => {
|
||||
calledArgs = [cmd, args, callback];
|
||||
callback(null, "output-data", "");
|
||||
};
|
||||
|
||||
await convert("input.md", "markdown", "pdf", "output.pdf", undefined, mockExecFile);
|
||||
|
||||
expect(calledArgs[1][0]).toBe("--pdf-engine=xelatex");
|
||||
expect(calledArgs[1]).toContain("input.md");
|
||||
expect(calledArgs[1]).toContain("-f");
|
||||
expect(calledArgs[1]).toContain("markdown");
|
||||
expect(calledArgs[1]).toContain("-t");
|
||||
expect(calledArgs[1]).toContain("pdf");
|
||||
expect(calledArgs[1]).toContain("-o");
|
||||
expect(calledArgs[1]).toContain("output.pdf");
|
||||
});
|
||||
|
||||
test("should reject if execFile returns an error", async () => {
|
||||
mockExecFile = (cmd, args, callback) => callback(new Error("fail"), "", "");
|
||||
await expect(
|
||||
convert("input.md", "markdown", "html", "output.html", undefined, mockExecFile),
|
||||
).rejects.toMatch(/error: Error: fail/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { beforeEach, expect, test } from "bun:test";
|
||||
import { convert } from "../../src/converters/pdftops";
|
||||
import { runCommonTests } from "./helpers/commonTests";
|
||||
|
||||
runCommonTests(convert);
|
||||
|
||||
let calls: string[][] = [];
|
||||
|
||||
function mockExecFile(
|
||||
_cmd: string,
|
||||
args: string[],
|
||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||
) {
|
||||
calls.push(args);
|
||||
if (args.includes("fail.pdf")) {
|
||||
callback(new Error("mock failure"), "", "Fake stderr: fail");
|
||||
} else {
|
||||
callback(null, "Fake stdout", "");
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
calls = [];
|
||||
});
|
||||
|
||||
test("converts a normal file to ps", async () => {
|
||||
const originalConsoleLog = console.log;
|
||||
|
||||
let loggedMessage = "";
|
||||
console.log = (msg) => {
|
||||
loggedMessage = msg;
|
||||
};
|
||||
|
||||
const result = await convert("in.pdf", "pdf", "ps", "out.ps", undefined, mockExecFile);
|
||||
|
||||
console.log = originalConsoleLog;
|
||||
|
||||
expect(result).toBe("Done");
|
||||
expect(calls[0]).toEqual(["in.pdf", "out.ps"]);
|
||||
expect(loggedMessage).toBe("stdout: Fake stdout");
|
||||
});
|
||||
|
||||
test("adds -eps flag for eps output", async () => {
|
||||
const result = await convert("in.pdf", "pdf", "eps", "out.eps", undefined, mockExecFile);
|
||||
|
||||
expect(result).toBe("Done");
|
||||
expect(calls[0]).toEqual(["-eps", "in.pdf", "out.eps"]);
|
||||
});
|
||||
|
||||
test("fails on exec error", async () => {
|
||||
expect(convert("fail.pdf", "pdf", "ps", "output.ps", undefined, mockExecFile)).rejects.toMatch(
|
||||
/error: Error: mock failure/,
|
||||
);
|
||||
});
|
||||
|
||||
test("logs stderr when execFile returns only stderr and no error", async () => {
|
||||
const originalConsoleError = console.error;
|
||||
|
||||
let loggedMessage = "";
|
||||
console.error = (msg) => {
|
||||
loggedMessage = msg;
|
||||
};
|
||||
|
||||
const mockExecFileStderrOnly = (
|
||||
_cmd: string,
|
||||
_args: string[],
|
||||
callback: (err: Error | null, stdout: string, stderr: string) => void,
|
||||
) => {
|
||||
callback(null, "", "Only stderr output");
|
||||
};
|
||||
|
||||
await convert("input.pdf", "pdf", "ps", "output.ps", undefined, mockExecFileStderrOnly);
|
||||
|
||||
console.error = originalConsoleError;
|
||||
|
||||
expect(loggedMessage).toBe("stderr: Only stderr output");
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import { expect, test, describe } from "bun:test";
|
||||
import { convert, parseVCF, toCSV } from "../../src/converters/vcf";
|
||||
|
||||
describe("parseVCF", () => {
|
||||
test("should parse a simple VCF card", () => {
|
||||
const vcfData = `BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
FN:John Doe
|
||||
N:Doe;John;;;
|
||||
TEL:+123456789
|
||||
EMAIL:john@example.com
|
||||
ORG:Example Corp
|
||||
END:VCARD`;
|
||||
|
||||
const result = parseVCF(vcfData);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
"Full Name": "John Doe",
|
||||
"Last Name": "Doe",
|
||||
"First Name": "John",
|
||||
Phone: "+123456789",
|
||||
Email: "john@example.com",
|
||||
Organization: "Example Corp",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("should handle multiple cards", () => {
|
||||
const vcfData = `BEGIN:VCARD
|
||||
FN:John Doe
|
||||
END:VCARD
|
||||
BEGIN:VCARD
|
||||
FN:Jane Smith
|
||||
END:VCARD`;
|
||||
|
||||
const result = parseVCF(vcfData);
|
||||
|
||||
expect(result).toEqual([{ "Full Name": "John Doe" }, { "Full Name": "Jane Smith" }]);
|
||||
});
|
||||
|
||||
test("should parse VCF with TYPE parameters", () => {
|
||||
const vcfData = `BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
FN:John Doe
|
||||
N:Doe;John;;;
|
||||
TEL;TYPE=WORK,VOICE:(111) 555-1212
|
||||
EMAIL;TYPE=PREF,INTERNET:john.doe@example.com
|
||||
END:VCARD`;
|
||||
|
||||
const result = parseVCF(vcfData);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
"Full Name": "John Doe",
|
||||
"Last Name": "Doe",
|
||||
"First Name": "John",
|
||||
Phone: "(111) 555-1212",
|
||||
Email: "john.doe@example.com",
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toCSV", () => {
|
||||
test("should convert contacts to CSV", () => {
|
||||
const contacts = [
|
||||
{
|
||||
"Full Name": "John Doe",
|
||||
Phone: "+123",
|
||||
Email: "john@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const result = toCSV(contacts);
|
||||
|
||||
expect(result).toBe('Full Name,Phone,Email\n"John Doe","+123","john@example.com"');
|
||||
});
|
||||
|
||||
test("should escape quotes", () => {
|
||||
const contacts = [{ "Full Name": 'John "Johnny" Doe' }];
|
||||
|
||||
const result = toCSV(contacts);
|
||||
|
||||
expect(result).toBe('Full Name\n"John ""Johnny"" Doe"');
|
||||
});
|
||||
|
||||
test("should handle empty data", () => {
|
||||
const result = toCSV([]);
|
||||
expect(result).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("convert", () => {
|
||||
test("should be a function", () => {
|
||||
expect(typeof convert).toBe("function");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { beforeEach, expect, test, describe } from "bun:test";
|
||||
import { convert } from "../../src/converters/vtracer";
|
||||
import type { ExecFileFn } from "../../src/converters/types";
|
||||
|
||||
describe("convert", () => {
|
||||
let mockExecFile: ExecFileFn;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecFile = (cmd, args, callback) => callback(null, "output-data", "");
|
||||
});
|
||||
|
||||
test("should call vtracer with correct arguments (minimal)", async () => {
|
||||
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
|
||||
mockExecFile = (cmd, args, callback) => {
|
||||
calledArgs = [cmd, args, callback];
|
||||
callback(null, "output-data", "");
|
||||
};
|
||||
|
||||
const result = await convert("input.png", "png", "svg", "output.svg", undefined, mockExecFile);
|
||||
|
||||
expect(calledArgs[0]).toBe("vtracer");
|
||||
expect(calledArgs[1]).toEqual(["--input", "input.png", "--output", "output.svg"]);
|
||||
expect(result).toBe("Done");
|
||||
});
|
||||
|
||||
test("should add options as arguments", async () => {
|
||||
let calledArgs: Parameters<ExecFileFn> = ["", [], () => {}];
|
||||
mockExecFile = (cmd, args, callback) => {
|
||||
calledArgs = [cmd, args, callback];
|
||||
callback(null, "output-data", "");
|
||||
};
|
||||
|
||||
const options = {
|
||||
colormode: "color",
|
||||
hierarchical: "true",
|
||||
filter_speckle: 5,
|
||||
path_precision: 0.8,
|
||||
};
|
||||
|
||||
await convert("input.png", "png", "svg", "output.svg", options, mockExecFile);
|
||||
|
||||
expect(calledArgs[1]).toContain("--colormode");
|
||||
expect(calledArgs[1]).toContain("color");
|
||||
expect(calledArgs[1]).toContain("--hierarchical");
|
||||
expect(calledArgs[1]).toContain("true");
|
||||
expect(calledArgs[1]).toContain("--filter_speckle");
|
||||
expect(calledArgs[1]).toContain("5");
|
||||
expect(calledArgs[1]).toContain("--path_precision");
|
||||
expect(calledArgs[1]).toContain("0.8");
|
||||
});
|
||||
|
||||
test("should reject if execFile returns an error", async () => {
|
||||
mockExecFile = (cmd, args, callback) => callback(new Error("fail"), "", "stderr output");
|
||||
await expect(
|
||||
convert("input.png", "png", "svg", "output.svg", undefined, mockExecFile),
|
||||
).rejects.toMatch(/error: Error: fail\nstderr: stderr output/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,224 @@
|
||||
import { test, expect, beforeEach, afterEach, afterAll } from "bun:test";
|
||||
import { Database } from "bun:sqlite";
|
||||
import { unlinkSync, existsSync, mkdirSync } from "node:fs";
|
||||
|
||||
// set environment variable to ensure the test database is used instead of production data
|
||||
process.env.DB_PATH = "./data/test-isolated.sqlite";
|
||||
|
||||
// dynamic import ensures that db.ts is loaded after the env is set
|
||||
let initializeDatabase: (db: Database) => void;
|
||||
let defaultDb: Database | undefined;
|
||||
await import("../../src/db/db").then((mod) => {
|
||||
initializeDatabase = mod.initializeDatabase;
|
||||
defaultDb = mod.default as Database | undefined;
|
||||
});
|
||||
|
||||
// Type-safe helpers for database query results
|
||||
interface DbTable {
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface DbVersion {
|
||||
user_version?: number;
|
||||
}
|
||||
|
||||
interface DbJournalMode {
|
||||
journal_mode?: string;
|
||||
}
|
||||
|
||||
interface DbColumnInfo {
|
||||
name: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface DbUser {
|
||||
id?: number;
|
||||
email?: string;
|
||||
}
|
||||
|
||||
function queryAllTables(database: Database): DbTable[] {
|
||||
return database
|
||||
.query("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
|
||||
.all() as DbTable[];
|
||||
}
|
||||
|
||||
function getDbVersion(database: Database): number | undefined {
|
||||
const result = database.query("PRAGMA user_version").get() as DbVersion;
|
||||
return result.user_version;
|
||||
}
|
||||
|
||||
function getJournalMode(database: Database): string | undefined {
|
||||
const result = database.query("PRAGMA journal_mode").get() as DbJournalMode;
|
||||
return result.journal_mode;
|
||||
}
|
||||
|
||||
function getColumnInfo(database: Database, table: string): DbColumnInfo[] {
|
||||
return database.query(`PRAGMA table_info(${table})`).all() as DbColumnInfo[];
|
||||
}
|
||||
|
||||
// Test database initialization and migration paths
|
||||
let testDbPath: string;
|
||||
let testDb: Database;
|
||||
|
||||
beforeEach(() => {
|
||||
mkdirSync("./data", { recursive: true });
|
||||
testDbPath = `./data/test-db-${Date.now()}.sqlite`;
|
||||
testDb = new Database(testDbPath, { create: true });
|
||||
// Now uses the real initialization logic from db.ts
|
||||
initializeDatabase(testDb);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (testDb) {
|
||||
testDb.close();
|
||||
}
|
||||
if (existsSync(testDbPath)) {
|
||||
unlinkSync(testDbPath);
|
||||
}
|
||||
if (existsSync(`${testDbPath}-wal`)) {
|
||||
try {
|
||||
unlinkSync(`${testDbPath}-wal`);
|
||||
} catch (err) {
|
||||
// WAL file cleanup error - log but don't fail test
|
||||
if (err instanceof Error && err.message.includes("ENOENT")) {
|
||||
// File already gone, which is fine
|
||||
}
|
||||
}
|
||||
}
|
||||
if (existsSync(`${testDbPath}-shm`)) {
|
||||
try {
|
||||
unlinkSync(`${testDbPath}-shm`);
|
||||
} catch (err) {
|
||||
// SHM file cleanup error - log but don't fail test
|
||||
if (err instanceof Error && err.message.includes("ENOENT")) {
|
||||
// File already gone, which is fine
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Close the module-level default database before cleanup to prevent file lock errors
|
||||
if (defaultDb) {
|
||||
defaultDb.close();
|
||||
}
|
||||
// Cleanup of the isolated test database after the test run
|
||||
if (existsSync("./data/test-isolated.sqlite")) {
|
||||
unlinkSync("./data/test-isolated.sqlite");
|
||||
}
|
||||
if (existsSync("./data/test-isolated.sqlite-wal")) {
|
||||
try {
|
||||
unlinkSync("./data/test-isolated.sqlite-wal");
|
||||
} catch (err) {
|
||||
// WAL file cleanup error - log but don't fail test
|
||||
if (err instanceof Error && err.message.includes("ENOENT")) {
|
||||
// File already gone, which is fine
|
||||
}
|
||||
}
|
||||
}
|
||||
if (existsSync("./data/test-isolated.sqlite-shm")) {
|
||||
try {
|
||||
unlinkSync("./data/test-isolated.sqlite-shm");
|
||||
} catch (err) {
|
||||
// SHM file cleanup error - log but don't fail test
|
||||
if (err instanceof Error && err.message.includes("ENOENT")) {
|
||||
// File already gone, which is fine
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("db initializes and creates tables on first run", () => {
|
||||
const tables = queryAllTables(testDb);
|
||||
expect(tables.length).toBeGreaterThanOrEqual(3);
|
||||
expect(tables.map((t) => t.name)).toContain("users");
|
||||
expect(tables.map((t) => t.name)).toContain("jobs");
|
||||
expect(tables.map((t) => t.name)).toContain("file_names");
|
||||
expect(getDbVersion(testDb)).toBe(1);
|
||||
});
|
||||
|
||||
test("db handles migration from version 0 to version 1", () => {
|
||||
testDb.close();
|
||||
const migrateDbPath = `./data/test-db-migrate-${Date.now()}.sqlite`;
|
||||
const migrateDb = new Database(migrateDbPath, { create: true });
|
||||
|
||||
try {
|
||||
// Simulates a real v0 database state
|
||||
migrateDb.exec(`
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS file_names (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
job_id INTEGER NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
output_file_name TEXT NOT NULL,
|
||||
FOREIGN KEY (job_id) REFERENCES jobs(id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS jobs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
date_created TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
||||
);
|
||||
PRAGMA user_version = 0;
|
||||
`);
|
||||
|
||||
// Now runs the real migration logic from db.ts
|
||||
initializeDatabase(migrateDb);
|
||||
|
||||
expect(getDbVersion(migrateDb)).toBe(1);
|
||||
const columnInfo = getColumnInfo(migrateDb, "file_names");
|
||||
expect(columnInfo.map((c) => c.name)).toContain("status");
|
||||
} finally {
|
||||
if (migrateDb) migrateDb.close();
|
||||
if (existsSync(migrateDbPath)) unlinkSync(migrateDbPath);
|
||||
if (existsSync(`${migrateDbPath}-wal`)) unlinkSync(`${migrateDbPath}-wal`);
|
||||
if (existsSync(`${migrateDbPath}-shm`)) unlinkSync(`${migrateDbPath}-shm`);
|
||||
}
|
||||
});
|
||||
|
||||
test("db enables WAL mode", () => {
|
||||
expect(getJournalMode(testDb)?.toLowerCase()).toBe("wal");
|
||||
});
|
||||
|
||||
test("db module exports a working database instance", () => {
|
||||
expect(defaultDb).toBeTruthy();
|
||||
const tables = queryAllTables(defaultDb!);
|
||||
expect(tables.length).toBeGreaterThanOrEqual(3);
|
||||
const tableNames = tables.map((t) => t.name);
|
||||
expect(tableNames).toContain("users");
|
||||
expect(tableNames).toContain("jobs");
|
||||
expect(tableNames).toContain("file_names");
|
||||
});
|
||||
|
||||
test("db has correct schema with status column", () => {
|
||||
// Verify file_names table has the status column (created during initialization)
|
||||
const columns = getColumnInfo(testDb, "file_names");
|
||||
const columnNames = columns.map((c) => c.name);
|
||||
expect(columnNames).toContain("status");
|
||||
expect(columnNames).toContain("job_id");
|
||||
expect(columnNames).toContain("file_name");
|
||||
expect(columnNames).toContain("output_file_name");
|
||||
});
|
||||
|
||||
test("db can insert and query data", () => {
|
||||
// Test that the database is functional
|
||||
// Insert a test user
|
||||
const stmt = testDb.prepare("INSERT INTO users (email, password) VALUES (?, ?)");
|
||||
const result = stmt.run("test@example.com", "hashedpassword");
|
||||
// Verify that the insert happened (run() returns result object)
|
||||
expect(result).toBeTruthy();
|
||||
|
||||
// Query the inserted user
|
||||
const user = testDb
|
||||
.query("SELECT * FROM users WHERE email = ?")
|
||||
.get("test@example.com") as DbUser;
|
||||
expect(user).toBeTruthy();
|
||||
expect(user.email).toBe("test@example.com");
|
||||
|
||||
// Cleanup
|
||||
testDb.query("DELETE FROM users WHERE email = ?").run("test@example.com");
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { normalizeFiletype, normalizeOutputFiletype } from "../../src/helpers/normalizeFiletype";
|
||||
|
||||
test("normalizeFiletype maps known inputs", () => {
|
||||
expect(normalizeFiletype("jfif")).toBe("jpeg");
|
||||
expect(normalizeFiletype("jpg")).toBe("jpeg");
|
||||
expect(normalizeFiletype("HTM")).toBe("html");
|
||||
expect(normalizeFiletype("tex")).toBe("latex");
|
||||
expect(normalizeFiletype("md")).toBe("markdown");
|
||||
expect(normalizeFiletype("unknown")).toBe("m4a");
|
||||
expect(normalizeFiletype("SVG")).toBe("svg");
|
||||
});
|
||||
|
||||
test("normalizeOutputFiletype maps known outputs", () => {
|
||||
expect(normalizeOutputFiletype("jpeg")).toBe("jpg");
|
||||
expect(normalizeOutputFiletype("latex")).toBe("tex");
|
||||
expect(normalizeOutputFiletype("markdown")).toBe("md");
|
||||
expect(normalizeOutputFiletype("markdown_mmd")).toBe("md");
|
||||
expect(normalizeOutputFiletype("glb2")).toBe("glb");
|
||||
expect(normalizeOutputFiletype("gltf2")).toBe("gltf");
|
||||
expect(normalizeOutputFiletype("objnomtl")).toBe("obj");
|
||||
expect(normalizeOutputFiletype("stlb")).toBe("stl");
|
||||
expect(normalizeOutputFiletype("plyb")).toBe("ply");
|
||||
expect(normalizeOutputFiletype("fbxa")).toBe("fbx");
|
||||
expect(normalizeOutputFiletype("assjson")).toBe("json");
|
||||
expect(normalizeOutputFiletype("WeIrDCase")).toBe("weirdcase");
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
import { afterEach, expect, mock, spyOn, test } from "bun:test";
|
||||
import { exec } from "node:child_process";
|
||||
import { readFile } from "node:fs";
|
||||
|
||||
// mocks have to be defined before importing the module under test,
|
||||
// otherwise the real modules will be loaded first and the mocks won't take effect.
|
||||
mock.module("node:child_process", () => ({
|
||||
// The mock checks the environment variable MOCK_EXEC_ERROR at call-time so
|
||||
// individual tests can trigger error paths by setting that env before
|
||||
// importing the module under test.
|
||||
exec: mock((cmd: string, cb: (error: Error | null, stdout: string) => void) => {
|
||||
const shouldError = (process.env.MOCK_EXEC_ERROR || "")
|
||||
.split(",")
|
||||
.some((p) => p && cmd.includes(p));
|
||||
|
||||
if (shouldError) {
|
||||
cb(new Error(`${cmd} not found`), "");
|
||||
return;
|
||||
}
|
||||
|
||||
// resvg, bun, heif-info, dasel, and the perl one-liner (msgconvert) print just
|
||||
// the bare version number — the source code itself prepends the tool name as a label.
|
||||
if (
|
||||
cmd.startsWith("resvg") ||
|
||||
cmd.startsWith("bun") ||
|
||||
cmd.startsWith("heif-info") ||
|
||||
cmd.startsWith("dasel") ||
|
||||
cmd.startsWith("perl")
|
||||
) {
|
||||
cb(null, "1.0.0\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// assimp's real output is multi-line; the source reads line index 5.
|
||||
if (cmd.startsWith("assimp")) {
|
||||
cb(null, "l1\nl2\nl3\nl4\nl5\nVersion 1.0.0 (GIT commit abc123)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// magick prefixes its version line with "Version: ".
|
||||
if (cmd.startsWith("magick")) {
|
||||
cb(null, "Version: ImageMagick v1.0.0\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cb(null, `${cmd} v1.0.0\n`);
|
||||
}),
|
||||
}));
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
readFile: mock(
|
||||
(path: string, encoding: string, cb: (error: Error | null, data: string) => void) => {
|
||||
cb(null, 'PRETTY_NAME="Ubuntu 22.04 LTS"\n');
|
||||
},
|
||||
),
|
||||
}));
|
||||
|
||||
mock.module("../../package.json", () => ({
|
||||
version: "1.0.0-test",
|
||||
}));
|
||||
|
||||
// We import the module in each test after the spies and the desired NODE_ENV were set
|
||||
// so that the top level execution is under control of each test
|
||||
|
||||
let consoleLogSpy: ReturnType<typeof spyOn>;
|
||||
let consoleErrorSpy: ReturnType<typeof spyOn>;
|
||||
|
||||
afterEach(() => {
|
||||
consoleLogSpy.mockRestore();
|
||||
consoleErrorSpy.mockRestore();
|
||||
delete process.env.NODE_ENV;
|
||||
delete process.env.MOCK_EXEC_ERROR;
|
||||
});
|
||||
|
||||
test("prints only the version and terminates itself immediately when NODE_ENV is not production", async () => {
|
||||
consoleLogSpy = spyOn(console, "log");
|
||||
consoleErrorSpy = spyOn(console, "error");
|
||||
|
||||
// import the module after the spies were set so that the initial console.log within the module
|
||||
// gets recognized by the spy
|
||||
await import("../../src/helpers/printVersions?test=" + Math.random());
|
||||
|
||||
// empty the queue to make sure all callbacks have been processed
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("ConvertX v1.0.0-test");
|
||||
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
||||
expect(readFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("prints system information and tool versions in production mode", async () => {
|
||||
process.env.NODE_ENV = "production";
|
||||
consoleLogSpy = spyOn(console, "log");
|
||||
consoleErrorSpy = spyOn(console, "error");
|
||||
|
||||
// import module under test after setting NODE_ENV and activating the spies
|
||||
await import("../../src/helpers/printVersions?test=" + Math.random());
|
||||
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("ConvertX v1.0.0-test");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("Ubuntu 22.04 LTS");
|
||||
expect(readFile).toHaveBeenCalledTimes(1);
|
||||
expect(exec).toHaveBeenCalledTimes(17); // Corresponds exactly to the number of exec calls in the source code
|
||||
});
|
||||
|
||||
test("logs error paths when tools are missing", async () => {
|
||||
process.env.NODE_ENV = "production";
|
||||
// Trigger a few error paths ("perl" is the msgconvert version lookup)
|
||||
process.env.MOCK_EXEC_ERROR = "pandoc,resvg,bun,perl";
|
||||
consoleLogSpy = spyOn(console, "log");
|
||||
consoleErrorSpy = spyOn(console, "error");
|
||||
|
||||
await import("../../src/helpers/printVersions?test=" + Math.random());
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith("Pandoc is not installed.");
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith("resvg is not installed");
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith("Bun is not installed. wait what");
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
"msgconvert (libemail-outlook-message-perl) is not installed",
|
||||
);
|
||||
});
|
||||
|
||||
test("processes output parsing correctly and logs no errors in the success case", async () => {
|
||||
process.env.NODE_ENV = "production";
|
||||
consoleLogSpy = spyOn(console, "log");
|
||||
consoleErrorSpy = spyOn(console, "error");
|
||||
|
||||
// import module under test after setting NODE_ENV and activating the spies
|
||||
await import("../../src/helpers/printVersions?test=" + Math.random());
|
||||
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
// testing some specific outputs
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("pandoc -v v1.0.0");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("ffmpeg -version v1.0.0");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("resvg v1.0.0");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("Bun v1.0.0");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("dasel 1.0.0");
|
||||
expect(consoleLogSpy).toHaveBeenCalledWith("msgconvert v1.0.0");
|
||||
|
||||
// make sure that error paths have not been triggered
|
||||
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { Elysia } from "elysia";
|
||||
import { isHtmlPageRequest } from "../../src/helpers/isHtmlPageRequest";
|
||||
import { userService } from "../../src/services/user";
|
||||
|
||||
const app = new Elysia()
|
||||
.use(userService)
|
||||
.get("/protected", () => "protected", { auth: true })
|
||||
.post("/protected", () => "protected", { auth: true });
|
||||
|
||||
test("identifies HTML page navigation requests", () => {
|
||||
expect(
|
||||
isHtmlPageRequest(
|
||||
new Request("http://localhost/protected", {
|
||||
headers: { accept: "text/html" },
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isHtmlPageRequest(
|
||||
new Request("http://localhost/protected", {
|
||||
headers: { accept: "TEXT/HTML" },
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isHtmlPageRequest(
|
||||
new Request("http://localhost/protected", {
|
||||
method: "HEAD",
|
||||
headers: { accept: "text/html,application/xhtml+xml" },
|
||||
}),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("does not identify API requests as HTML page navigation", () => {
|
||||
expect(
|
||||
isHtmlPageRequest(
|
||||
new Request("http://localhost/protected", {
|
||||
method: "POST",
|
||||
headers: { accept: "text/html" },
|
||||
}),
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
isHtmlPageRequest(
|
||||
new Request("http://localhost/protected", {
|
||||
headers: { accept: "application/json" },
|
||||
}),
|
||||
),
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
isHtmlPageRequest(
|
||||
new Request("http://localhost/protected", {
|
||||
headers: { accept: "*/*" },
|
||||
}),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test("redirects unauthorized HTML requests to login", async () => {
|
||||
const response = await app.handle(
|
||||
new Request("http://localhost/protected", {
|
||||
headers: { accept: "text/html" },
|
||||
redirect: "manual",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBe(302);
|
||||
expect(response.headers.get("location")).toBe("/login");
|
||||
});
|
||||
|
||||
test("returns JSON 401 for unauthorized API requests", async () => {
|
||||
const response = await app.handle(
|
||||
new Request("http://localhost/protected", {
|
||||
method: "POST",
|
||||
headers: { accept: "application/json" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(response.status).toBe(401);
|
||||
expect(await response.json()).toEqual({ success: false, message: "Unauthorized" });
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { buildDownloadUrl } from "../../src/helpers/buildDownloadUrl";
|
||||
|
||||
test("encodes reserved characters in download filenames", () => {
|
||||
expect(buildDownloadUrl("", "1/2/", "clip #1?.gif")).toBe("/download/1/2/clip%20%231%3F.gif");
|
||||
});
|
||||
|
||||
test("preserves output path segments while encoding the filename", () => {
|
||||
expect(buildDownloadUrl("/convertx", "user/job/", "報告 100%.pdf")).toBe(
|
||||
"/convertx/download/user/job/%E5%A0%B1%E5%91%8A%20100%25.pdf",
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user