From a8b74878b63433a808f91bd028b659c5cb65f660 Mon Sep 17 00:00:00 2001 From: Kevin Goedecke Date: Fri, 22 Nov 2024 12:37:02 +0000 Subject: [PATCH 01/25] docs: reword challenge for video extractor tool --- README.md | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 6f26e1e..8258ae6 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,24 @@ -# SlideSpeak coding challenge: Build a PowerPoint to PDF marketing tool +# SlideSpeak coding challenge: Build a PowerPoint Video Extractor Tool ## The challenge! -Build a front-end implementation as well as a back-end service to convert PowerPoint documents to PDF format. This -should be done by implementing a simple **Next.js** front-end that posts a file to a **Python** server. You don’t have -to do the converting logic yourself as you can use unoconv or unoserver to do this (you can see more about this in the -acceptance criteria). The front-end is also already implemented in the /frontend folder. You only need to add the +Build a front-end implementation as well as a back-end service to extract videos from PowerPoint documents. This +should be done by implementing a simple **Next.js** front-end that posts a file to a **Python** server. +The front-end is also already implemented in the /frontend folder. You only need to add the necessary logic to switch between the steps and convert the file via the API that you're going to build. -- Webpage for the - tool: [https://slidespeak.co/free-tools/convert-powerpoint-to-pdf/](https://slidespeak.co/free-tools/convert-powerpoint-to-pdf/) -- Design: [https://www.figma.com/file/CRfT0MVMqIV8rAK6HgSnKA/SlideSpeak-Coding-Challenge?type=design&t=6m2fFDaRs72CowZH-6](https://www.figma.com/file/CRfT0MVMqIV8rAK6HgSnKA/SlideSpeak-Coding-Challenge?type=design&t=6m2fFDaRs72CowZH-6) +- The tool will be on a webpage similar to: [https://slidespeak.co/free-tools/convert-powerpoint-to-pdf/](https://slidespeak.co/free-tools/convert-powerpoint-to-pdf/) +- Figma Design: [https://www.figma.com/design/CRfT0MVMqIV8rAK6HgSnKA/SlideSpeak-Coding-Challenge?node-id=798-61](https://www.figma.com/design/CRfT0MVMqIV8rAK6HgSnKA/SlideSpeak-Coding-Challenge?node-id=798-61) ## Acceptance criteria ### Back-end API - Should be implemented in Python. -- Converting PowerPoints to PDF can be done with `unoconv` or `unoserver` via Docker if you want to be fancy πŸ˜€. You - don’t need to implement the converting logic yourself. - - [Documentation on how to use unoconv and spawn a process](https://pypi.org/project/unoconv/) - - Note: `unoconv` is deprecated but thats ok for this challenge - - [How to use unoserver via docker](https://gist.github.com/kgoedecke/44955d0b0b1ed4112bcfd3e237e135c0), this will - create an API that you can use based on [this](https://github.com/libreofficedocker/unoserver-rest-api) - documentation. - - Using unoserver is nice-to-have (but the preferred way), if you find unoconv easier use it instead -- The API should consist of one endpoint (POST /convert), which should do the following: - 1. Converts the attached file to PDF - 2. Uploads the PowerPoint and PDF file to Amazon S3 +- Extracting Videos from PowerPoint using a zip utility. This should support multiple processes in parallel. Preferably with a queue. +- The API should consist of one endpoint (POST /extract), which should do the following: + 1. Extracts the videos from the PowerPoint + 2. Uploads the videos to Amazon S3 via [boto3](https://boto3.amazonaws.com/v1/documentation/api/latest/index.html) 3. Creates a presigned URL for the user to download @@ -35,7 +26,7 @@ necessary logic to switch between the steps and convert the file via the API tha [https://medium.com/@aidan.hallett/securing-aws-s3-uploads-using-presigned-urls-aa821c13ae8d](https://medium.com/@aidan.hallett/securing-aws-s3-uploads-using-presigned-urls-aa821c13ae8d) - 4. Returns the presigned S3 url to the client which allows the user to download the file (by opening the url in new + 4. Returns the presigned S3 url/urls to the client which allows the user to download the file (by opening the url in new tab) ### Front-end app @@ -45,11 +36,11 @@ necessary logic to switch between the steps and convert the file via the API tha ## Nice to haves / tips -- Uses unoserver to convert PowerPoint to PDF via docker compose +- Uses a queuing system like Celery and Redis - The logic of the front-end ideally should not rely on useEffect too much since it can be difficult to track what is happening - Tests - Use conventional commit message style: https://www.conventionalcommits.org/en/v1.0.0/ - Lint your code - Keep commits clean -- If you want to be really fancy you can add queuing with Celery \ No newline at end of file +- Setup with Docker Compose From 0ed764c9a851b9c90b17ef552336453444a9b6c8 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 17:51:50 +0100 Subject: [PATCH 02/25] chore: Dockerize bun application --- frontend/Dockerfile | 17 +++++++++++++++++ frontend/docker-compose.yml | 8 ++++++++ 2 files changed, 25 insertions(+) create mode 100644 frontend/Dockerfile create mode 100644 frontend/docker-compose.yml diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..6101262 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,17 @@ +# Use the Bun image as the base image +FROM oven/bun:latest + +# Set the working directory in the container +WORKDIR /app + +# Copy the current directory contents into the container at /app +COPY . . + +# Expose the port on which the API will listen +EXPOSE 3000 + +# install bun deps +RUN bun install + +# Run the server when the container launches +CMD ["bun", "run", "dev"] \ No newline at end of file diff --git a/frontend/docker-compose.yml b/frontend/docker-compose.yml new file mode 100644 index 0000000..6030f11 --- /dev/null +++ b/frontend/docker-compose.yml @@ -0,0 +1,8 @@ +services: + server: + image: bun-server + build: + context: . + dockerfile: Dockerfile + ports: + - "3000:3000" From ecc96bc71d9c413d6686b0c8e04a2e3a98b9dac7 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 17:52:19 +0100 Subject: [PATCH 03/25] docs: Update frontend README.md to add Docker instructions --- frontend/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/frontend/README.md b/frontend/README.md index 0314c72..6629d80 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -35,3 +35,20 @@ bun run dev ``` Open [http://localhost:3000](http://localhost:3000) with your browser. + +--- + +## Docker + +If you rather, you can choose to run the bun server on a docker contaiers + +```bash +docker compose up --build +``` + + +#### Run in detach mode: + +```bash +docker compose up --build -d +``` \ No newline at end of file From 864615a838cdfc427c64aa76f249541796675361 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 18:26:38 +0100 Subject: [PATCH 04/25] chore: Created FastAPI application directory and main --- backend/app/__init__.py | 0 backend/app/main.py | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 backend/app/__init__.py create mode 100644 backend/app/main.py diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..d786432 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,8 @@ +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +async def root(): + return {"message": "Hello World"} \ No newline at end of file From eba5fa66d88ba92b55ad2135fe526226d35f3523 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 18:27:54 +0100 Subject: [PATCH 05/25] chore: Dockerized FastAPI server, Redis server + unoserver Set up .env for later (AWS secret & bucket) Relevant Files: `requirements.py` for an easier instalation --- backend/.env.example | 3 +++ backend/.gitignore | 1 + backend/Dockerfile | 16 ++++++++++++++++ backend/docker-compose.yml | 23 ++++++++++++++++++++--- backend/requirements.txt | 8 ++++++++ 5 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 backend/.env.example create mode 100644 backend/.gitignore create mode 100644 backend/Dockerfile create mode 100644 backend/requirements.txt diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..8a8c9ec --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,3 @@ +AWS_ACCESS_KEY= +AWS_SECRET_KEY= +S3_BUCKET_NAME= diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..9c87bac --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.9-slim + +WORKDIR /backend + +# Install system dependencies (unzip) +RUN apt-get update && apt-get install -y unzip + +# Install Python dependencies +COPY requirements.txt . +RUN pip install -r requirements.txt + +# Copy application code +COPY ./app /backend/app + +# Run the FastAPI app +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 7fb21b2..c904641 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,5 +1,22 @@ -version: '3' - +version: "3.9" services: + backend: + build: . + ports: + - "8000:8000" + depends_on: + - unoserver + - redis + env_file: + - path: ".env" + required: true + unoserver: - image: libreofficedocker/libreoffice-unoserver:3.19-9c28c22 \ No newline at end of file + image: libreofficedocker/libreoffice-unoserver:3.19-9c28c22 + ports: + - "2002:2002" + + redis: + image: redis:6.2 + ports: + - "6342:6342" diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..31a0695 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,8 @@ +fastapi +uvicorn +unoserver +boto3 +python-multipart +celery +redis +aiofiles From c7ff14d3148e30c366af2480348c665930f6fc36 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 18:32:12 +0100 Subject: [PATCH 06/25] docs: Added to README -> pulgamecanica walkthrough part 1 --- backend/README.md | 82 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/backend/README.md b/backend/README.md index 2533c50..a8aeff1 100644 --- a/backend/README.md +++ b/backend/README.md @@ -6,4 +6,84 @@ ```bash docker compose up --build -``` \ No newline at end of file +``` + +*** + +## Pulgamecanica walkthoguh + + +### Analysis + +#### How Videos Are Stored in PowerPoint Files? + + PowerPoint files with the .pptx extension are essentially ZIP archives that follow the Office Open XML standard. + + They contain various directories and XML files for slides, images, audio, and video. Videos embedded in a PowerPoint are usually stored as media files within the ppt/media folder inside the ZIP archive. + + +*** + +#### How SlideSpeak Extracts Videos from .pptx Files + + Treat .pptx as a ZIP Archive: Use unzip tool extract its contents. + +```py +subprocess.run( + ["unzip", "-j", pptx_file_path, "ppt/media/*", "-d", output_path], + check=True, + shell=False, + ) +``` + +As seen here: https://github.com/SlideSpeak/image-extractor-cli/blob/30c5ad96ffbc3aaea63b01928630b3efe87e62e9/image_extractor.py#L110C9-L114C10 + + + unzip: Unzips the file. + -j: Junk the directory structure (extracts files without retaining folder hierarchy). + pptx_file_path: Path to the .pptx file. + ppt/media/*: Extracts only files from the ppt/media/ directory. + -d output_path: Specifies the directory to extract the files. + + + Any file with video formats such as .mp4, .mov, .avi, etc., is likely a video. + + Extract Relevant Files: Extract the video files into a temporary directory for further processing or upload. + +*** + +### Coding + +Now we know what the python script should look like. + +Instead of using the unzip tool we will use unoserver which is more appropiate and also suggested implicitly by the docker-compose providede challenge file. + + +#### Structure + +We will use fastapi to create an endpoint where we can `post` pptx files and get a response appropiate for the desired output. (likely to be a ref. to the S3 bucket were we will store the videos) + +``` +backend/ +β”œβ”€β”€ app/ +β”‚ β”œβ”€β”€ __init__.py +β”‚ β”œβ”€β”€ main.py # Entry point for FastAPI app +β”‚ β”œβ”€β”€ tasks.py # Logic for video extraction and S3 upload +β”‚ β”œβ”€β”€ celery_worker.py # Celery worker setup +β”œβ”€β”€ requirements.txt +β”œβ”€β”€ Dockerfile +β”œβ”€β”€ docker-compose.yml +β”œβ”€β”€ README.md +└── .env +``` + +### Dependencies + +- fastapi: Framework for building the backend API. +- uvicorn: ASGI server for running the FastAPI application. +- unoserver: A tool for handling pptx files thorugh a server +- boto3: AWS SDK for Python to interact with Amazon S3. +- python-multipart: Required by FastAPI to handle file uploads. +- celery: Task queue system for parallel processing. +- redis: Backend for Celery and message broker. +- aiofiles: Asynchronous file I/O for FastAPI when saving uploaded files From 325b59543087e520dd0ef77b2989df6d0f89cf7c Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 20:44:15 +0100 Subject: [PATCH 07/25] chore: extract_videos.py tool is working The tool is missing the queue and redis setup. Currently the endpoint is calling the extract_video function and waiting for the urls response. I must implement with celery a queue with redis by using the `delay` python decorator. --- backend/.env.example | 1 + backend/Dockerfile | 6 +- backend/README.md | 15 +++ backend/app/__pycache__/main.cpython-39.pyc | Bin 0 -> 818 bytes .../video_extractor.cpython-39.pyc | Bin 0 -> 3005 bytes backend/app/celery_worker.py | 4 + backend/app/main.py | 23 +++- backend/app/video_extractor.py | 118 ++++++++++++++++++ backend/docker-compose.yml | 9 +- backend/requirements.txt | 1 - 10 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 backend/app/__pycache__/main.cpython-39.pyc create mode 100644 backend/app/__pycache__/video_extractor.cpython-39.pyc create mode 100644 backend/app/celery_worker.py create mode 100644 backend/app/video_extractor.py diff --git a/backend/.env.example b/backend/.env.example index 8a8c9ec..67a9c87 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,3 +1,4 @@ AWS_ACCESS_KEY= AWS_SECRET_KEY= S3_BUCKET_NAME= +AWS_REGION= \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 9c87bac..6badfe2 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.9-slim -WORKDIR /backend +WORKDIR /app # Install system dependencies (unzip) RUN apt-get update && apt-get install -y unzip @@ -10,7 +10,7 @@ COPY requirements.txt . RUN pip install -r requirements.txt # Copy application code -COPY ./app /backend/app +COPY ./app /app # Run the FastAPI app -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/backend/README.md b/backend/README.md index a8aeff1..b5ec79e 100644 --- a/backend/README.md +++ b/backend/README.md @@ -58,6 +58,20 @@ Now we know what the python script should look like. Instead of using the unzip tool we will use unoserver which is more appropiate and also suggested implicitly by the docker-compose providede challenge file. +Fast API route: +**POST** _/extract_ + +- @params: + - file: file.pptx + +##### NOTE: + I am happy :D !!! + I learnt something new! + How to make a test with postman when a post requires a named file parameter. + You should go to postman and change the request to a post. + Then on the Body section choose form-data. + Then hover on the `Key` section and choose "File" on the dropdown. + Then you can put the key `file` and choose the file for the value. #### Structure @@ -87,3 +101,4 @@ backend/ - celery: Task queue system for parallel processing. - redis: Backend for Celery and message broker. - aiofiles: Asynchronous file I/O for FastAPI when saving uploaded files +- watchgod: For file reloading \ No newline at end of file diff --git a/backend/app/__pycache__/main.cpython-39.pyc b/backend/app/__pycache__/main.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab46430e3eaa8081b5d97ab72ee6504cb8a1dcc5 GIT binary patch literal 818 zcmY*XyKWRQ6tz8`N0!Y)1R=^TGX=XKT7&`;QMQRzA_y@O8kw;-3>l9{wl|N}Y{Swb z=mBX{@(1*M1C3iMeu0YX(H4m<-zy(oAKzmiwz=6uFn<2tKY0@%^v7?m4+@)oSmpo% zM;up3;tWfY5gBBGq#4B?Csin;EW$|OCCXwEuM!?yVL>?khIz=Nk93K%4IaNhY4R7i zP)c2Vz>Ix58N1%grc#U_l$CIshsVd0=NE-&Y^iFu{frf-CtA&Fe$33NNwM1%7gn>v z=4T}r%H$UCc`{m~P(Y_ZRA7J0dW4V@SifKy3!z2tLCk5O?^?_W4>@ldtGI zSzsHqm`6+Ud2c~lw4zsN3_=glB527fY(X4GuK=MimjOog&O>Cf2MBlbHAEY=c!?L3 z$1N?f7k)G!v!oZoBeBdPy>}4Z{gGnWr5rbLxrUN#ZsB@tws;q5SHB5=SWq@;L@m-x$SCWWKEeMhxm+=b`5v_eNvk%l^k zlx9%fut>-d35Kz#={h*Kgcnj~* z1nWD19i_c2o`Qu~Q)&W$1_z_AMeRR4+wKgKuQ`?OdUF9>n9)WV+p%OkcS zut)07@^V%GboFOHUtZ}k-`SDrfskc=CmbAzgqPj>+x_7F+S+=*A3R)tT=o0wYn$tz zRsH^I@bT8#Lv%Op|8~7>>iL`Nzy9QR8$MI3Jl5Ug7gg2+w7go}&>J_r0_i;|@ z1wEzeOU!Q-^lUzF!s+e&H%$-ETACaRnR(fs@Rr8o?8w`VV&P?}7kV;$;_*m|L6*v6 zPi1m4$R<*F6BQ*xZ!5{8@se5OGa%-Qqex{zdf?Y4dL(Bb?B^}4xI6-0;qomQ_=6rR zYlA&8I4EmsPsDM4!z|+s%rO*P=oiEG~!R@v=T-8i=DzhT3mSm$&M7JA zA(0;z?4)kS+=5_~ol;8v2rGi7mjA-wyVxO@ z(rA1eWq26rc6zAGgTws#Rx(kOF!rh-Oywxde&Qvmm%H9>I!U?z+d7FF6B zdjo4#T-STNJ3}?7CI$M+dYy_Ow=qS{p&+(PZL3Y^SeIU>*O>ePY;TBy1;e>09HNuI z4{T@wnuD;QIQ(yMs(YM0XIdnlS=*#fAO!aDJxcToQr9%9k4f?&?9TBR@&HLO^57Kg zjP6Dr>QnXwgFKj8%LA_iPj`mr);LZ>4#uBxzrX4sw!NW9gbXu*k+Bpi8YVz>YxC0z zPro^Y2&OyxxWCrlk0;RG%(otj<7f^kN!uQUG8`$tv3@j$pVTKw{ti^ugM6N8J0J#= zHNZ>xqG6*qF~`vw0~zYCLJE5IvY}RkK^!3?_^oP!;iq)rKL6U;NrUrq1``=e?dU2# zaDsstX8^fpm5o=-cCaj@U0%~OHH}}xyFJ?{O9Qp~0AoteA~vI4YJ;vWWiQ=Y8zk*g z`94P8psA+lImnv`-GZEesGOYw8c>o==~pEGE(7cZeG1?-+)pVetaAd>mV&7xUKU z_NmDS(^}Rl?4tIRO`XCzX%~*6`=5Bbu>Nn?zhAiPzb@YO)w#Z+a^s>o$w#klggLh^ zS{?RJFZwS6xPt)h=D)16nE7t-^ap~DnHf(jeVR#A=snVM@CYJMWsWL{Ur?`vdIXig z=W{b@L!5uEg4cYAk+_zp1+@h&@kj*0fyOWHxPf+O!GQEc}~UHdr*Td#q{Ctp8!Q z7Pv0G_Ogo+aBBrC^yaJk>f8CwzyL1wmzH1KNwaj-zoM%_D#emGqq;HzP&&{lksM0> zNh~!jjcCXQTBTEIiu|EA>r`tzT=`3H``R^)I+#S@el$w?Bo=pdLQ+d8TwJ5Af7UFE XzjW$N93C&OIJ14{xbC7m@6P`ZkVqVo literal 0 HcmV?d00001 diff --git a/backend/app/celery_worker.py b/backend/app/celery_worker.py new file mode 100644 index 0000000..cb26b8d --- /dev/null +++ b/backend/app/celery_worker.py @@ -0,0 +1,4 @@ +from app.tasks import celery + +if __name__ == "__main__": + celery.start() \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py index d786432..6b743ea 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,8 +1,21 @@ -from fastapi import FastAPI +from fastapi import FastAPI, UploadFile, HTTPException, BackgroundTasks +from video_extractor import extract_videos_task app = FastAPI() - -@app.get("/") -async def root(): - return {"message": "Hello World"} \ No newline at end of file +@app.post("/extract") +async def extract_videos(file: UploadFile): + # if file.content_type != "application/vnd.openxmlformats-officedocument.presentationml.presentation": + if file.content_type != "application/vnd.openxmlformats-officedocument.presentationml.presentation": + raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PowerPoint file.") + + # Save file temporarily + temp_path = f"/tmp/{file.filename}" + with open(temp_path, "wb") as f: + content = await file.read() + f.write(content) + + # Queue the extraction task + # task_id = extract_videos_task.delay(temp_path) + task_id = extract_videos_task(temp_path) + return {"task_id": task_id} diff --git a/backend/app/video_extractor.py b/backend/app/video_extractor.py new file mode 100644 index 0000000..316e42f --- /dev/null +++ b/backend/app/video_extractor.py @@ -0,0 +1,118 @@ +import os +import subprocess +from celery import Celery +import boto3 +from botocore.exceptions import NoCredentialsError + +# Setup Celery +celery = Celery( + "tasks", + broker="redis://redis:6379/0", + backend="redis://redis:6379/0", +) + +# S3 Configuration +AWS_ACCESS_KEY = os.getenv("AWS_ACCESS_KEY") +AWS_SECRET_KEY = os.getenv("AWS_SECRET_KEY") +S3_BUCKET_NAME = os.getenv("S3_BUCKET_NAME") +AWS_REGION = os.getenv("AWS_REGION") + +# AWS S3 Client +s3_client = boto3.client( + "s3", + aws_access_key_id=AWS_ACCESS_KEY, + aws_secret_access_key=AWS_SECRET_KEY, + region_name=AWS_REGION, +) + + +def convert_pptx_with_unzip(pptx_file_path, output_dir): + """ + Convert the .pptx file to a raw directory structure using Unzip. + """ + os.makedirs(output_dir, exist_ok=True) + unzip_command = [ + "unzip", + "-j", + pptx_file_path, + "ppt/media/*", + "-d", + output_dir, + ] + + try: + subprocess.run(unzip_command, check=True, shell=False) + except subprocess.CalledProcessError as e: + raise RuntimeError(f"Unzip conversion failed: {e}") + if not os.path.exists(output_dir): + raise RuntimeError(f"Unzip didnt provide the output file: {output_dir}") + + + +def extract_videos_from_directory(input_dir): + """ + Extract video files from the converted directory. + """ + video_extensions = {".mp4", ".mov", ".avi", ".mkv"} + videos = [] + + # Look for media files in the `ppt/media/` directory + if not os.path.exists(input_dir): + raise RuntimeError(f"Unusual pptx format, no {input_dir} founded") + + for file in os.listdir(input_dir): + if os.path.splitext(file)[1].lower() in video_extensions: + videos.append(os.path.join(input_dir, file)) + + return videos + + +def upload_to_s3(file_path, s3_key): + """ + Upload a file to S3 and generate a presigned URL. + """ + try: + s3_client.upload_file(file_path, S3_BUCKET_NAME, s3_key) + return s3_client.generate_presigned_url( + "get_object", + Params={"Bucket": S3_BUCKET_NAME, "Key": s3_key}, + ExpiresIn=3600, # URL expires in 1 hour + ) + except NoCredentialsError: + raise RuntimeError("AWS credentials not found.") + except Exception as e: + raise RuntimeError(f"Error uploading to S3: {e}") + + +# @celery.task +def extract_videos_task(file_path): + """ + Celery task to extract videos from a PowerPoint file. + """ + output_dir = f"{file_path.replace(' ', '')}_output" + try: + # Step 1: Convert the PowerPoint file to a ZIP-like structure using Unzip + convert_pptx_with_unzip(file_path, output_dir) + + # Step 2: Extract videos from the converted directory + extracted_videos = extract_videos_from_directory(output_dir) + print("Extracted Videos", extracted_videos) + + if not extracted_videos: + return {"message": "No videos found in the presentation.", "urls": []} + + # # Step 3: Upload videos to S3 and generate presigned URLs + presigned_urls = [] + for video in extracted_videos: + s3_key = f"videos/{os.path.basename(video)}" + presigned_url = upload_to_s3(video, s3_key) + presigned_urls.append(presigned_url) + + return {"message": "Videos extracted and uploaded successfully.", "urls": presigned_urls} + + finally: + # Clean up temporary files and directories + if os.path.exists(file_path): + os.remove(file_path) + if os.path.exists(output_dir): + subprocess.run(["rm", "-rf", output_dir], check=False) diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index c904641..d0f76e3 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -5,16 +5,13 @@ services: ports: - "8000:8000" depends_on: - - unoserver - redis + volumes: + - ./app:/app env_file: - path: ".env" required: true - - unoserver: - image: libreofficedocker/libreoffice-unoserver:3.19-9c28c22 - ports: - - "2002:2002" + restart: always redis: image: redis:6.2 diff --git a/backend/requirements.txt b/backend/requirements.txt index 31a0695..7b6c448 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,6 +1,5 @@ fastapi uvicorn -unoserver boto3 python-multipart celery From d1f107dc49de8db60f3c3e7d079d4f23a812ed4a Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 21:38:00 +0100 Subject: [PATCH 08/25] fix: fixed typo on frontend README --- frontend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/README.md b/frontend/README.md index 6629d80..ecb8277 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -40,7 +40,7 @@ Open [http://localhost:3000](http://localhost:3000) with your browser. ## Docker -If you rather, you can choose to run the bun server on a docker contaiers +If you rather, you can choose to run the bun server on a docker container ```bash docker compose up --build From 11a9c86abfdfbde2f1bd448c41065701113e0a72 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 21:38:29 +0100 Subject: [PATCH 09/25] chore: Added __pycache__ to gitignore --- backend/.gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/.gitignore b/backend/.gitignore index 2eea525..e4ffba9 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1 +1,3 @@ -.env \ No newline at end of file +.env +app/__pycache__ +app/__pycache__/* \ No newline at end of file From ef063312d1bfe60f4ca833a00d7d80ac55b7572b Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 21:41:26 +0100 Subject: [PATCH 10/25] chore: Enable celery The conversion process is now running with a celery worker queue --- backend/.env.example | 3 +- backend/app/celery_worker.py | 4 +-- backend/app/main.py | 14 ++++++--- backend/app/video_extractor.py | 56 ++++++++++++++++++---------------- backend/docker-compose.yml | 2 +- 5 files changed, 44 insertions(+), 35 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 67a9c87..b073baa 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,5 @@ AWS_ACCESS_KEY= AWS_SECRET_KEY= S3_BUCKET_NAME= -AWS_REGION= \ No newline at end of file +AWS_REGION= +REDIS_PORT= \ No newline at end of file diff --git a/backend/app/celery_worker.py b/backend/app/celery_worker.py index cb26b8d..9df5f03 100644 --- a/backend/app/celery_worker.py +++ b/backend/app/celery_worker.py @@ -1,4 +1,4 @@ -from app.tasks import celery +from video_extractor import celery if __name__ == "__main__": - celery.start() \ No newline at end of file + celery.start() diff --git a/backend/app/main.py b/backend/app/main.py index 6b743ea..8ee5bf0 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,5 +1,6 @@ from fastapi import FastAPI, UploadFile, HTTPException, BackgroundTasks -from video_extractor import extract_videos_task +from video_extractor import extract_videos_task, convert_pptx_with_unzip +import os app = FastAPI() @@ -14,8 +15,11 @@ async def extract_videos(file: UploadFile): with open(temp_path, "wb") as f: content = await file.read() f.write(content) - + # Queue the extraction task - # task_id = extract_videos_task.delay(temp_path) - task_id = extract_videos_task(temp_path) - return {"task_id": task_id} + videos = extract_videos_task(temp_path) + + # Clean up temporary files and directories + if os.path.exists(temp_path): + os.remove(temp_path) + return {"task": videos} diff --git a/backend/app/video_extractor.py b/backend/app/video_extractor.py index 316e42f..05190cb 100644 --- a/backend/app/video_extractor.py +++ b/backend/app/video_extractor.py @@ -4,11 +4,15 @@ import boto3 from botocore.exceptions import NoCredentialsError +# Redis Configuration +REDIS_PORT = os.getenv("REDIS_PORT") + # Setup Celery celery = Celery( - "tasks", + "extract_videos_task", + backend=f"redis://redis:6379/0", broker="redis://redis:6379/0", - backend="redis://redis:6379/0", + ) # S3 Configuration @@ -44,9 +48,9 @@ def convert_pptx_with_unzip(pptx_file_path, output_dir): subprocess.run(unzip_command, check=True, shell=False) except subprocess.CalledProcessError as e: raise RuntimeError(f"Unzip conversion failed: {e}") - if not os.path.exists(output_dir): - raise RuntimeError(f"Unzip didnt provide the output file: {output_dir}") - + finally: + if not os.path.exists(output_dir): + raise RuntimeError(f"Unzip didnt provide the output file: {output_dir}") def extract_videos_from_directory(input_dir): @@ -84,35 +88,35 @@ def upload_to_s3(file_path, s3_key): raise RuntimeError(f"Error uploading to S3: {e}") -# @celery.task +@celery.task def extract_videos_task(file_path): """ Celery task to extract videos from a PowerPoint file. """ output_dir = f"{file_path.replace(' ', '')}_output" - try: - # Step 1: Convert the PowerPoint file to a ZIP-like structure using Unzip - convert_pptx_with_unzip(file_path, output_dir) + # Make sure path does not exist, could exist if an error occured and didnt cleanup + if os.path.exists(output_dir): + subprocess.run(["rm", "-rf", output_dir], check=False) - # Step 2: Extract videos from the converted directory - extracted_videos = extract_videos_from_directory(output_dir) - print("Extracted Videos", extracted_videos) + # Step 1: Convert the PowerPoint file to a ZIP-like structure using Unzip + convert_pptx_with_unzip(file_path, output_dir) - if not extracted_videos: - return {"message": "No videos found in the presentation.", "urls": []} + # Step 2: Extract videos from the converted directory + extracted_videos = extract_videos_from_directory(output_dir) + print("Extracted Videos", extracted_videos) - # # Step 3: Upload videos to S3 and generate presigned URLs - presigned_urls = [] - for video in extracted_videos: - s3_key = f"videos/{os.path.basename(video)}" - presigned_url = upload_to_s3(video, s3_key) - presigned_urls.append(presigned_url) + if not extracted_videos: + return {"message": "No videos found in the presentation.", "urls": []} - return {"message": "Videos extracted and uploaded successfully.", "urls": presigned_urls} + # # Step 3: Upload videos to S3 and generate presigned URLs + presigned_urls = [] + for video in extracted_videos: + s3_key = f"videos/{os.path.basename(video)}" + presigned_url = upload_to_s3(video, s3_key) + presigned_urls.append(presigned_url) - finally: - # Clean up temporary files and directories - if os.path.exists(file_path): - os.remove(file_path) - if os.path.exists(output_dir): + # Make sure path does not exist after job is finished + if os.path.exists(output_dir): subprocess.run(["rm", "-rf", output_dir], check=False) + + return {"message": "Videos extracted and uploaded successfully.", "urls": presigned_urls} diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index d0f76e3..53565eb 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -16,4 +16,4 @@ services: redis: image: redis:6.2 ports: - - "6342:6342" + - "6342:6379" From ad72e1d3bfc7f3fb9354de7825fbe5d0e54119f6 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Fri, 22 Nov 2024 22:45:24 +0100 Subject: [PATCH 11/25] fix: Remove unused files --- backend/README.md | 14 ++++---------- backend/app/__pycache__/main.cpython-39.pyc | Bin 818 -> 0 bytes .../__pycache__/video_extractor.cpython-39.pyc | Bin 3005 -> 0 bytes backend/app/celery_worker.py | 4 ---- 4 files changed, 4 insertions(+), 14 deletions(-) delete mode 100644 backend/app/__pycache__/main.cpython-39.pyc delete mode 100644 backend/app/__pycache__/video_extractor.cpython-39.pyc delete mode 100644 backend/app/celery_worker.py diff --git a/backend/README.md b/backend/README.md index b5ec79e..0618ae5 100644 --- a/backend/README.md +++ b/backend/README.md @@ -13,9 +13,7 @@ docker compose up --build ## Pulgamecanica walkthoguh -### Analysis - -#### How Videos Are Stored in PowerPoint Files? +### How Videos Are Stored in PowerPoint Files? PowerPoint files with the .pptx extension are essentially ZIP archives that follow the Office Open XML standard. @@ -24,7 +22,7 @@ docker compose up --build *** -#### How SlideSpeak Extracts Videos from .pptx Files +### How SlideSpeak Extracts Videos from .pptx Files Treat .pptx as a ZIP Archive: Use unzip tool extract its contents. @@ -56,8 +54,6 @@ As seen here: https://github.com/SlideSpeak/image-extractor-cli/blob/30c5ad96ffb Now we know what the python script should look like. -Instead of using the unzip tool we will use unoserver which is more appropiate and also suggested implicitly by the docker-compose providede challenge file. - Fast API route: **POST** _/extract_ @@ -73,7 +69,7 @@ Fast API route: Then hover on the `Key` section and choose "File" on the dropdown. Then you can put the key `file` and choose the file for the value. -#### Structure +### Structure We will use fastapi to create an endpoint where we can `post` pptx files and get a response appropiate for the desired output. (likely to be a ref. to the S3 bucket were we will store the videos) @@ -95,10 +91,8 @@ backend/ - fastapi: Framework for building the backend API. - uvicorn: ASGI server for running the FastAPI application. -- unoserver: A tool for handling pptx files thorugh a server - boto3: AWS SDK for Python to interact with Amazon S3. - python-multipart: Required by FastAPI to handle file uploads. - celery: Task queue system for parallel processing. - redis: Backend for Celery and message broker. -- aiofiles: Asynchronous file I/O for FastAPI when saving uploaded files -- watchgod: For file reloading \ No newline at end of file +- aiofiles: Asynchronous file I/O for FastAPI when saving uploaded files \ No newline at end of file diff --git a/backend/app/__pycache__/main.cpython-39.pyc b/backend/app/__pycache__/main.cpython-39.pyc deleted file mode 100644 index ab46430e3eaa8081b5d97ab72ee6504cb8a1dcc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 818 zcmY*XyKWRQ6tz8`N0!Y)1R=^TGX=XKT7&`;QMQRzA_y@O8kw;-3>l9{wl|N}Y{Swb z=mBX{@(1*M1C3iMeu0YX(H4m<-zy(oAKzmiwz=6uFn<2tKY0@%^v7?m4+@)oSmpo% zM;up3;tWfY5gBBGq#4B?Csin;EW$|OCCXwEuM!?yVL>?khIz=Nk93K%4IaNhY4R7i zP)c2Vz>Ix58N1%grc#U_l$CIshsVd0=NE-&Y^iFu{frf-CtA&Fe$33NNwM1%7gn>v z=4T}r%H$UCc`{m~P(Y_ZRA7J0dW4V@SifKy3!z2tLCk5O?^?_W4>@ldtGI zSzsHqm`6+Ud2c~lw4zsN3_=glB527fY(X4GuK=MimjOog&O>Cf2MBlbHAEY=c!?L3 z$1N?f7k)G!v!oZoBeBdPy>}4Z{gGnWr5rbLxrUN#ZsB@tws;q5SHB5=SWq@;L@m-x$SCWWKEeMhxm+=b`5v_eNvk%l^k zlx9%fut>-d35Kz#={h*Kgcnj~* z1nWD19i_c2o`Qu~Q)&W$1_z_AMeRR4+wKgKuQ`?OdUF9>n9)WV+p%OkcS zut)07@^V%GboFOHUtZ}k-`SDrfskc=CmbAzgqPj>+x_7F+S+=*A3R)tT=o0wYn$tz zRsH^I@bT8#Lv%Op|8~7>>iL`Nzy9QR8$MI3Jl5Ug7gg2+w7go}&>J_r0_i;|@ z1wEzeOU!Q-^lUzF!s+e&H%$-ETACaRnR(fs@Rr8o?8w`VV&P?}7kV;$;_*m|L6*v6 zPi1m4$R<*F6BQ*xZ!5{8@se5OGa%-Qqex{zdf?Y4dL(Bb?B^}4xI6-0;qomQ_=6rR zYlA&8I4EmsPsDM4!z|+s%rO*P=oiEG~!R@v=T-8i=DzhT3mSm$&M7JA zA(0;z?4)kS+=5_~ol;8v2rGi7mjA-wyVxO@ z(rA1eWq26rc6zAGgTws#Rx(kOF!rh-Oywxde&Qvmm%H9>I!U?z+d7FF6B zdjo4#T-STNJ3}?7CI$M+dYy_Ow=qS{p&+(PZL3Y^SeIU>*O>ePY;TBy1;e>09HNuI z4{T@wnuD;QIQ(yMs(YM0XIdnlS=*#fAO!aDJxcToQr9%9k4f?&?9TBR@&HLO^57Kg zjP6Dr>QnXwgFKj8%LA_iPj`mr);LZ>4#uBxzrX4sw!NW9gbXu*k+Bpi8YVz>YxC0z zPro^Y2&OyxxWCrlk0;RG%(otj<7f^kN!uQUG8`$tv3@j$pVTKw{ti^ugM6N8J0J#= zHNZ>xqG6*qF~`vw0~zYCLJE5IvY}RkK^!3?_^oP!;iq)rKL6U;NrUrq1``=e?dU2# zaDsstX8^fpm5o=-cCaj@U0%~OHH}}xyFJ?{O9Qp~0AoteA~vI4YJ;vWWiQ=Y8zk*g z`94P8psA+lImnv`-GZEesGOYw8c>o==~pEGE(7cZeG1?-+)pVetaAd>mV&7xUKU z_NmDS(^}Rl?4tIRO`XCzX%~*6`=5Bbu>Nn?zhAiPzb@YO)w#Z+a^s>o$w#klggLh^ zS{?RJFZwS6xPt)h=D)16nE7t-^ap~DnHf(jeVR#A=snVM@CYJMWsWL{Ur?`vdIXig z=W{b@L!5uEg4cYAk+_zp1+@h&@kj*0fyOWHxPf+O!GQEc}~UHdr*Td#q{Ctp8!Q z7Pv0G_Ogo+aBBrC^yaJk>f8CwzyL1wmzH1KNwaj-zoM%_D#emGqq;HzP&&{lksM0> zNh~!jjcCXQTBTEIiu|EA>r`tzT=`3H``R^)I+#S@el$w?Bo=pdLQ+d8TwJ5Af7UFE XzjW$N93C&OIJ14{xbC7m@6P`ZkVqVo diff --git a/backend/app/celery_worker.py b/backend/app/celery_worker.py deleted file mode 100644 index 9df5f03..0000000 --- a/backend/app/celery_worker.py +++ /dev/null @@ -1,4 +0,0 @@ -from video_extractor import celery - -if __name__ == "__main__": - celery.start() From 15e360966abc9353957401718fbcce678f1380dc Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Sat, 23 Nov 2024 14:44:48 +0100 Subject: [PATCH 12/25] chore: Added frontend functionalities --- frontend/bun.lockb | Bin 178194 -> 178882 bytes frontend/package-lock.json | 5970 +++++++++++++++++ frontend/package.json | 1 + .../src/components/ChooseFileStep/index.tsx | 15 +- .../src/components/ConvertFileStep/index.tsx | 95 +- .../src/components/DownloadFileStep/index.tsx | 31 +- .../PowerPointToPdfConverter/index.tsx | 46 +- 7 files changed, 6080 insertions(+), 78 deletions(-) create mode 100644 frontend/package-lock.json diff --git a/frontend/bun.lockb b/frontend/bun.lockb index 2f9561a533df12fff7716884ed395066f3ad1ab8..4797cda01775de1c0dc882abb7a7f23813aefba6 100755 GIT binary patch delta 34147 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 delta 34003 zcmeIb2Xs|c*ZzC<>-^K6W&4R&4aOLvET?^v6fzX3Y*9e)#dnk2kzw-@=s%EtYny^VHs!twXj?Uu}Bi zvwU_|19S8%vy1L8&MBH8Ok`z`C)DF9sf-Lm{#ME3sf*l-6#iXg9pv*4{~%J*O+wa2 zW+Ow8S;#;c z$FmGY^kxp9Om5NZt1_cmyH(Xa9{!cQ%9js0;2OKw(RrhDS`8oS>4eU|lHeM4y6l3< zV}{+4H{t2(s-fAmxMnT8)%khjvnS*fct%f=KJa+ zw_I?fR3g^SqK~E9XCS3vYls&)nX!@l8<3LkYUQ};Q)vJdmkfxI{KcM zOJ#WlIYl|h9q`ifrS7!Q+ANZjWKKJtiZv3nq-Cfbltn1=7fNEo=+VBE<)~$Jj%X=cFG#YNFKV*&n0= zz4d)lTiWf)Q1RY6VLPc!2D*n~*Y0*(w959jEF1K$3tCy@ls$fAe)jl+;v7YU zbmB~;bWYyLF=NN$cg5(^Nz#MaqY6AzQ*3=i-k9O73iCZ?s-6BMyyVMoHEQfIrYJereJFs?9X{AAB_adxpe1^MH1Xm%gc)goO{y4~FDVPnTjM`prH zKPU50EM@gke{vKulV$tqPca%?{9yT@}M zy4c>9^g+n2ep`=350Rzl2MUmg?Y->^Y7rqVKMF4u7WK2^w;*LuMvNWbDkpnb?ts2_ z{FXlU)|2PNcNt(WL0gg1u<4`|xgK45>`i14a;e12Qj|(SD$1gOn#c#qAeMJTm!XJu zWF05|9Pu(#dy!)4Mx?BHWyougtOVn8M&=YPLzjy33bMzK&&GSu#jtru8NyM>Vs?m< z!33mbosd#Mij$xrQsS#1r6GS}sl*rFXgBZxx-@t#Qha0)QW{i{GkRjqcv(^GMffCc zTMhnzBX=Mr-)C|3e{}-yI1x_|w#UX8Vvl7Myu{B!il0}##m;y;QX00!(Z}9u8?Xr} zmVM$#dZHj_g0(h|&d&FEZi1Jd>WmbF5^gQFyU2B#N^GyvGI#9VAvppsUG6%c>vZlA zxju0DP}xpqcXj!mc6XlxaZVSWz+!2UGedKVuvohC7Nk_%9VzjxRaBJ*#k2ElJ9p;V zY_r>K7dwi-OEo9arNg`3VVC0epSz9kAzu1(8&djrc+R-O>`@+1c2Q0%mXMqc4*xDP zkoaS)=92DP%4aSWm%Kd2CKAWm70W?nbY8)P+Y82yVJ-GFgO}CdDJR`6`F6uLAZ7O7 z4=)WEo-?#?BWanoO z9hKv`lZ>*=G^+=rUuDBV~$zgB0E~&F&9ZKR(qO zu;P**2uQ*1ag;Mi_NeUPIhXVQt)1>p=^An~4U44wL(^?TZob?0l%B|%o{FB5ucoVY z)#|6Ny2qXv8IB=INHJj54BG$($?6dI*fiu`yWM?MQ1$y(EU8{2$oHF<<26~TDl<{^|b*6E;`Daztu1UJDGqWv?IFB+SlYP_Bm<3LoSD?kAnaXIA zZ2nwLwQHL0ORIj7OiniLP-~lJ_&$Q`VpUKUmlA;6v_q*V?J;gvlbdCjbFWeBnx&gx zU8Bx4OZPRx|Lx?Y@Qp-EA-zXs)=M@Q)KKf1r-b_kGOl5_1DoX(Kw2IVM_VG2ZtfnhZ@PDZuArp4mewT357=#L-O= z$=(;yZcyjiruohhlGX=UC8V*0idIo&#w7dhMeBrSHOSmtSG9{v_f-#f8^9>}dZ0-q zGyv@$N3$AWz8|jI#i#quL-rs^1#v0g;0U({$?&A3RaRv!lYIu0CBdpsY)ibTiETM( zPUj>id)J_Kvf6)!kPN3)x;LRdjZ+=!rx~Nv+Qba=LVa~6G2O^fsYw~WH4QwTUT#vu zRBMwmjO)~CV#*>to)N@YKJ9H5g-xnMbeeA%A&zI#0W^IHn)o#uP1ujtNxhSlX2ht; zZ8N;H8am@;e&0}?X`Ak=*~oS~IwLMcO-|16Jq#o6RZ+&y+|@{(Nly1xX)H~aq3B9T z`jR2S{729(cD`>TnzZB^tDh5_*eR{{`o^N!^F{`30a|~%lzPeDBWUSTy0=wRX&)hD zoSNJ&!?y}uTnB5&brel(t6;fRY_p4_N=u5+x)A5J+O;0-lFMCiG)ifbY;;kn?K6CZ z%{`vBRtw6qQvy)LDCP=XcnnSatCCb>v{tDdGJN+(UtBJtlYQ@?NeZtjOG-A+MyoR& z(v3bUH9f;u(!ws+P?;^0%~dVby7Y8k56(O0Vv(bY^X9;(x=c^mZ`e5U9xu< zTC6(PAkF7v?n;x$LaMH4wgWMJeGfXCsmc9u-Of|-^kb3Id~cFHh|W7U*S>AqD&$skMXlZ^m%x>JVHQKfdyFz2^cQ#z-c zn_H`Oozsmhb-Htg?*x-W8qVTL>UcG|ONQ^|M0P30(bcpO^U>gnc?-4?T(?Fh#QC|T}ZKGQUXw9sI9tv+tI`W z@v@j?ZxuR|iCizu=&DkCW|;S;s3|?uePz&6Hi=jhs-@ZmSWCap?`X_7;_gS20kKn- zp-H|9R+~&Lvn!Ssc0`l)$zC-dLz6xuC4KfUG-(uCY)U|y-MK7Q_)Q-)$z~btog;D5 zUhjHBNwRMHP7{(QFf=S~@suhW-OoVfp-B_S%`W<4d$q1ly6-24q&8+pq)P3Z;k%)O z?b2{K)>CL=2kyxf*xW&#>6`8gpde{7dv=V>!O8tHyrpn`RfkS#zOM*jg~WD9Hd?CG z{u#bonHDm9tlC(z1Wi)di~dh&_WF@|M@mIfrC5v8?Sy1)rms_I2O6_M+L4s(+i)>X zv;cfv#+btC!QNApa!mSYf;o;CL7F6vC@bl)sU+6al4`;MT=GHh>1 z9lF|aXw?3Mqgl=MZbR#&I$YmAfK^4hh*4mfyIHNhA;b4P+_i9Y8?!M;rDkOq-K_6B z)!M8K-*tG0RLU`^U9#_GG)j}E*JoKq>!hZ}r1{$QbR9uCi_;P4RZ7Ns`zauwM$4t>o1#>??FN&l#XN`?ib$5gDZ1pNxhlfskfcN zF7+ogJV)xFy^(#aj)+b(hN{%T8Rq@{)Re*L-k%waZsP0SXi7^_?+3a$=>5=cDfrGn`^Dv@jW5iRAz@al%B-Y=rr%OgnC$^Ckgek8ox(k?b1kF|2mpx zl`)tQ?UY8+c}vioE~b-yL%YF>ljWc@jk%=FL$mYI8Q#5UgRL4f2hx>RXdazctbN9}9}PFfEy~i9y)jv`WJ(W>By_#i z>bD3n{l@v+u!Q^WM#Gg!D-QMv zn%HDHzOU9z{32_`iCc|ByW}F<(2|H_!DGD$RH-8}jP7dkhz#%jH_I@~9{D*TyR^*c zWN#g&AZO7LX>z_Enc-WGZo3a_insO>D+dhDRG>AjQh-CAHA!f=xvQ+ z2_d@LZm;(ji6pxy{vNq^?6_Z5`G=Kmr#Ny@keTQ*c>UvbTD+ zoNcF0Y#%_tae2;bub~aH`ag8&#nI=AVYoVdM~3gYi=6bOZI4b zr39eZy)RL7(3~=9%|Wyj%XF3^Z=;c76}{-alh9!LxqScuJGl%_t6a-$){q&5r2E+s z*-0wr*_zK;(I$_}@Xd#7OA0n*cJ)nY7pwKg-ENs3lV;qmCXdfBDyp^PGrYIl;qlz4 z&LyV#Rui(9=F+ywzDlF))q~Qx+USDTSDkB?=6j4#7iz%Sao}xe_9%*(fun81SWmgC z>VzgKIqNj%N(4=u!rJ1@4@RqYh3SSzO)ktZe;T9K6{dUJkCpbxL3bh{_N)$R-q#2v zsHwNM=Xz}2#B^`ne9Mj5nkNvFzQALs`4_a|mL|j9Yn;8h;(+6meRI(gh@-_rlFiS? zsdh!_-sa;yo}t$A{Qx1>M&}B4egQw7p|P*gfvqQaJoK7OhknWC$_Z-90?glik*yL+ePKy^jBk zX7`*NXd4vSJzm*r;B{!?*!GF(VKk}TK23dy)&{ML_=mT`Wa&w{NNGbT1>QQN_$H!B z33i@kXz6J7W_=j#1~j`>U8dMSIS$9c7u{yvIH7K)ahv%zO{FH zJgh--!NVlw61bSl`n%M+;&flNskZSR>+o?Mnp8>?8YlZ^qj9*95onNXgs8P88NLbA zTrP7kzgSFD?Ml-PubNz%!MS>EX@>8aV%H|Q=Ga!O+D%V4nybmvGkjx9JlqA6!;CoP zDl}=ft@%pr{l?b%qOtzjao(k99j&eVAR*}@pS8X>nQot1>^xKcqE)=RTzN*WI(-l8 z5}XV)1ID!4k0z7M%59{mwKFn|JJsnK8NN^Mu?NpOCYtB&QB&?sH#(`c_hy(=XQ(sx zrkfwkQ0?wZH!7&f_htCT-RrS_NUCJ5m>bags}3#Fj8K(2GwsHD9_zU?TWD}%(L{B1Ol&ErYLh>X(zFMc_`>=kKIc zz-lGGSj&RqsSH?_tWS9<6|v@6<+6BKl7+(hT$EzJ7cjp(Vi^<0`jnSaA(O@`!0s=* z-z0shmD-k-2;T>!;s=1_dl1M+r0|c3!pD`x5|D(CI&v0LJ|ZQ90uui?kWYCj4S2$0 zZ7J#J0ZIR)!;2JszN23zM_3V(@fjcmE(G#fXnp@xO1kG9zPywQo(CcqJMki=ycb1R z?>uO``YR|hVU`KYr@WNZZ&<7?CEY3@rLK1Pzmt-Gt&>iqbwA zv0`m0`n$jjK6H4I!hZy$!jB#O6Qq1Z3jZmPbe{qFeCE3PCBlvHcmfD+wpB|?#;<|M zEe6nH8z~==671vX<)u{E&*4Q%u)m}Kos=X4taKT6KpJqplToAu2Rgb)sVK|g z2RXb*LsfgUPO-!ec4CJ(u_9%zWIOy&hyQP+v?j+%Utap)$2h!5sW;zne3*dM>~T(l z@=_8Oz>8p8MGk^uia4f&FcO+|5}=q=EtAu*0@ z>BNhaV64NpMoNPckdm*hqo+FY?Hs)WQdXy~Nd6Ujy7Mg`ky1b}NA^KVR6j?*){z5| zQo)Um9D2wCIo{DHAuFR#LsmsTfRy}?AWa#xSp?*BB`F2ZCZ40!V-1R< z(cVQ9*ZIiFRbEQz0;<;#)6UvPAhLFhH8P52fE9_#gc8BLyMGT z*2()aDb5`y}Xoue5C@OAaSqrCBzCR;r~QR zg=@(t4SdVVFH#!zAyOLpkt5e5CEX{;YRD}W@B~TtErfi^OBs;ePR5^{jC-7P<)sw( zi<9nGCtjrFKZKO{!w&CCD0wQwQ}24dq=o-nJz4$Ki>{E*m85i&Ts6r@q;zRtQTT`y zeE^Vj|6D!&bM=H9@R6%1nSuXYJ;_y+%#DApp2}ZUNss(<_4LoxlRY>7xqABN>gk`W zCzeF}D(auBr+==V{y%y3bp9IonaQ)_rRN^B{6;-B;I%Au3T-7? z17*CPrIx%_P364qH=@*Xv_Y>|Q^CvpMkAHIEK3C}tESeYHBmv!v(y^2!sULWnJPoe zU0zK!dBbl+t8s5+sn9p7sjX-+Dr!ZR+JH7=g`Yc?o6w3@R8wu<^z)=+>6=-q`J2_$ zUbJ`>zcNehK%2ADZzQVSXfsz5UN2CT(Cw3TQX%2G=w+&kF!4)&q>Rn)uKhc@F~KaXT?LMwU~ z``+^#{Z#3D*!Ldxp$$;+?_(d@ocH}aV!In{=KI)J<~Iha*=5*QhJ9#RD)R&ELtFfT z-?&j7L!0{n_I>E*QIkaj_I-qX>-{`(v>a{FdhGky zZwyn}A7kIg*oT&*f<^>_Z!;;y=eev^k&qjRLhBZRY3L_l4gmRI|UpzAvy3ZIa6T z68q2=f9W?St7B+$zr?Y)3t$yQCRk{`XwqhUJY!$x^`_SfW^BYR-Mw__}`?mXyIcoNH?Awlg zXj*0Nz&^CaJN(8xbqsCp4(!|MH|DEFJF#yk_I=|wo>l|C!9KK=XbY6F3;VvQu14(Y zWh_$5(FX0pzHj};b1M5=?E4n`&=#wp@30T8@H@ZpqAEkn{SN!S_Zu&%ao=O#_t=NF zR7L%OeP}a&@EfnHO=v|wVBe2^<8@W~Bli7>eQ3*7{BG<+o3q<*tWdkrX70wmpZvy3 zHTx&*`w9EdR;$dPu@7zW&wgXAI)*m)XYAYKH{Mc<_F&&0?Az-%-cbYgVjtQ{wD*+p z3-;}0{C@EpWokLvpkJ`>SHJP0%KjDme#Jht^(tr|_MsK-^BbS2GPK-%*tg$re5%In z$G-j8hxWOOI)Hs>GY&~JRDN)KY+LF_}@q~Z@@AKIKle&cJk8*Szx z?EB4cY*n*=!@l3J4{f{3JdAy4ix2yao$46c+{4&+#Bc0Ui;iI55$rqaH@;H?j$$9$ zO0*x8aSZ#8V&5^pv0E)i8*~i&e)k(ctL)#g?|1A&+pB_(V;@@Kali4aDnrXXj(z|3 z8~fF`e`DXju@CK_iaLRPXfsaujo;KJw4xK(_lMs&qDue3zCW-J?U;%`iG65uPWp}G zYB$=_a=LGEZY4+TzoG)90`QYt{45TcIGtO&7Bh{Y8l!t^mA=2nCl z=!FQ^i@Xp$yb#_>5cTwcN)V@nSSdsUZB&L>QVAlbGDMVKF2tb95WxWujdXSZL_h$< zdLf$VARojUAqssE&2*U%xju*{fe_JpTp&bfAjDQ7Vsum$hz&x_r~=VSZxW)Y3PhW# z5Uq7-Rfy(Qjq3WBszxs(UdLC1*in@fbE=UdQSTOFW;KYe)gjvG+0`LZszaO*B3Wl% z1F=tt#n(Wj>SIF8y#``n4TvP;H1cLKN19=&s9z$gK_0Bp9Nn9v2J|8Vs>j2)~XB zf!H9#j1Y)EdXo@EArNiqK=jk4bs(D8f!Hg=039C+u|tSCp%B;U-9pR^h3FavF;LGA zgGdR3I3Yxq&aBJVJ|Py@g}6~46Jl;%h=Jh{H|s^=5Iw>nyb%yX^neJ6Q$nm1;#O_c zgIE#)ky8&MTQ3)4P(6s?`Vhl(c72F|`Vi}d$k9O!Al3*`*Z^XrE)ycR0YsBXh&(+m z5+XDbVyh5$=qToj-XO$`D2UN|lMqEw5N#SljMb$LA(}UY*ek?19p4CIhY)ibK@{lS zLdg7TVY7P+`4RN>5j)n+`hFCAe3?0-0VvP`mEgSXd@w6V00C7r)l|n4gMk2(L1c;nOh(&t25Q7pSf|DSg)7eQ70Z9<+g;=bE z+CZ!kqOc9bi@HpR+%^zR+Csdf$F+qBZ40qgh^0Cz8DfJFGm;@*)tiJUN``2Y0`a;o zO@U~hVpLnbH^rD~_?-VhQtyR%Vj=(w&%Mcu!HvB!I|Hg_CX zFY0NWG}O`Xih;jIIDgq2q0>7W-*|gOaxYIWYR8d@XuT}c2r#!aTz&XDW4;lPXa5fq zsjG){Ho{HssaVRAv>8^~_q&p|SG-i;(9nmv83Ep%ZJjo>wrX45vWJmx7>!mx-P34o z8ogF?Xq;a$_|u#0zde;7yxl()@3DGne`9O~^IWdf{jA}g&8@mXF-JM(w9GOh%ujD} zsv9+k>So^MG$=+I)WZFv+F_&eVs9_7e^7V-;I=&X&0YG1RO5ryHLf=%7Ig z=O~bldJ;l7c}#T&kk5RFlb+gZF&@uT4p#^LSBHDr;iUKWIovZ27lxhyq<0s{$na18 z2LsRhG)ua2p~Hm}E_Ezk)DcJtH@seCcO3e z98S8t2`IHG&sR>mrs&hL#d?l-BP0#89#py;DTQoy5;iA11IXuVC!IX*axai{Tb*<* z2tNiS{Wgb75!3lRzFI%B)@Z1kzhO*>l6(34z8Lg%MNSkYFRw7&HM*K{F5yVn9pK3dmHr z8{7l#2lBK?Um%`805k-RKx5DZi1)VuF(4MS25}$(SkKNNE65`l6$yAjB~TdzfIv_M z$ip~)2Ca1xlz@R}DKt)gq$TOAloaJG#4QvN83wDBU!1v$>@FUm_egZNhe(|)n zr-8WrLEs0y0e>uEJ@w7jC`sf$KJX-hBt7Fzqfv1qf-;2Sq>(_LwD=8d1>3-lv`L>1vG9Ukfq{X z_%iSr_#Au*HiAuH4Ojt|fmgxp;0~Fxqrn(37UYAzpdaWDdVubr6OdUqiN+Lx$zUqr z(I4v}%CS_wf%uO>HsME*gON`n=YdXyI|EsA(t$idn+kfq##b4THAvPRS!2EcUjkV( zHi7M62iOU|0lUC=;Ct``*aPjmR0LUX%`K14p@S~s~;S7*WIVqqz_(R<36yzKdNXM3dsYJ+= zck2j0115lOr0EWNfL_24I)c;S3^)r)Nhix+33;c3yTMHG7F=KAA41B5gAam-z{6m- zxRdOuO+hmd56+PAEbx-)C~`kI0D1x0j${{+9Y`MPdIscx5nv?91zkaR&>6^+eX`@o z6MwSH3J=V-=;6VJi~uecvpI6Dvi4nh@1}Y2KRt_!GjJSMk+81$c}TrqrWfD=<+H0#L$nf zHKK;RioO)Q3|;^-GoJ*HfCVIc068994|0I4an}Ly4{<=5Fyf4|W+j0{&;rO7*Gvbk zGqQr45|sJa5JZ7U5TWm1#}ueZFbFuuNJ~^l_kwUoHbB+~GWd-^6Hpb%1}jd{62yRb zAd7Y@5DVf!8z3vHtgO-%G96^POLx(2#g^-qpolZbJd}Z#IVly&S|jsH=GPc73gm%d zpe?us3<2q&14soaAQ`j=fuJ2obM%48uAmd>2r{JBOh@UA>;hx~6EBo4NpLOb31ni+ z0@oJ^FPuz^KA^Xwy9?p<=x*NY2xoyCz+i9_7zAzvH;dIn!ENAHknMy=AalTQFcRc~ zJHYKgdO&)n0dR*UpYT|RmoXgggeM|p5s@B~JcU3!cM7-*6pIT_1Jgk%co^IV#Fb|N z84}?i05ie;K+-%29s;s@Nr94H++EVj5IzQG1F7h7Ae@9R^M&H_&hWRsFptcC=L>Fc zJV`onuBU-GpUc-HEW5L8=d!K$2mR#vd08Lh!51Wu1J_GPIef_>tObw*SpZyfq#V!= z6J7ugf&*X=_!;a5KLY8!AHX+YJCL#83buf+!6xu8@Fn=1`mOEvQ{>xV6}TI`308nN zz;aLumVwv6tKd2C3XsMw0gFISun;T;FM=1q^FY!*3xpS4tH2V16G5z zU>$f1yaP6XkHNd(J+K~p1U>}PfHLqt_`nH^`~*mkZUkQeu~@>Jf$)OQWY%vZ&=>3g zJAw4X_uxCQ3w#TH0@8(2nRKg!rTe6pq=$Y1(lZjj7wiMSg8e`$J_O1S*Kg?daMAz& zqwyjmt+pEZzcO4h{wKgW;4wU&V~F3u1@I?01C9Vm^KT&WN1bpk@;H$6Bg=7;<_~cC zGVT=mNg(k8DX&-pZiHkw3nY=-C~1K3!kq__Nh*^@HKoB#fEP&m#z?uWi2_n?B(ec; z%P-$pF|fYDHc^oT^*|jE0es*Z5DvsrskAO(>5Wj`r_88R9E4f}R0oxS0o*po)rr(+ zBBf)bcxhWTN3V>m3aWrWAl1o5nxvIH0!c4Y@<>?9lqNa#6kjC8Ac=4jmDi=C+&Fi1 zCEeA+l3x12tz4vZqBJbb;U(|yvS&%BNCFp0U zjAZ%rZW)Y_H7F7%;StCj@!8>^d{ z`Er7mh82sSiStV$@h`b3bA3zvDgiwdNG3@mT__i2;>+bLajldxY@ z0GUBjv6Pt!+{RkHF5@rji)+c1GF+)3;lyGoRKhYmqRX&|?hdK+npi7V%VK-^kQIx= zxCMw)h+aO?g9kWr)k+1<^z#8pjSl>LJkMRfHz-NiQN{pi zgv`rQAdQf7%N;4Zz+I)P%sQ^7P)0!qQX;2r_-Z-5!#ZuA+zEhioBA@sUHhL^gE zFIw~wxl}IwmysCWkpxvqkoefJDP>PDoN5}0Fx6ms>#rn%`(_05g`=O1$Xyd-8f{|Z zW89Z5mo2$HuXh=%|gxNV7OlKd{kg5%55u#}kPK`usOWor-PQ zWcBM`8(}=BacrXzW-hC$t8Owvd48hFCZkWp)77n;3MJtUD=ZkWFln!87277Zt>@M1 z`VkVEtE%fIn~Vgblisz7Vg~3Fo45-ctfMy@F@e*9th+2FJA?Esn~mP){viG14@QD{ z9J!gye+KFIj~Su5+mA-;O7W@mbY*?#j}%iwXG^2%>etc2-PZs-F#h&;hVoGW4%oNa((b;=q7mclLj<`7|qGd~7NgHMMmAtr)bY zmcD1J(IT)F&LJLV^PxO-vS}+7_S6SOT&um?jF>?;2eTTG$bCb_j2eSSjk?EpN?IKo zA4e~^@2WWZ(bW8>wtw-pX}m`sIytxi8kZ$#`m4x04ID?~E|(URBAV z;WrKZuKoL6tkNx;W`*hMJMdS#2%h4|+<_NwsH+FVnp7}xhmp|c>K0w0&bE%N`a6ok zp-=8G63rbEy5&xzcU$+J8-e??>SZi^=nO4!T+w~2#_sc9zScac+FW9!-|4l_>+9Ec z8lmCt`#2^aTe)Lb#@n_VC5jvM)w_4nY&&5{#U#%Ik-Dna3^nIQ>bP$((2M?z4*} zp|r7HwTtc#=CKvCb|tGnqjiOEjnZ)U1selzJ9Od9tn5E1HG!#tF^%8S7MS;lIw5#nX*VHvpL zJ0h%EAEFn0$8$#Q^gG`feMnp7dsYDVJsbB`j((=kb8oz8S!vC{?mFXpqmQg4k9L={d{0qz^)Bc(SF|HR?8seATJBSE(|L6QwIWP5jcgRC; zJKc)&6zK*(8;KF_e~KS`!-DX7Hz$LX*fw}2zeL6B>U%N6eU(SMc>ncvOMiI9HI&n?xM`RX zqHo@dKX%hEA;R64g*>w8je+N;el^f)jO9I&M zfB8LcpRlZDf^!m0)EoC13E}QrXNuOINcOhtxs((#TAUZGBErJm*U+Rq(dzN24{Oq; z)0iC6rFH_7{b{OkMc^ z`>^{4nbt3+zyHgOp`72a-8$sVY^U=N7$ru6K1M>j&qDOTgDfN+biqLuyY21uw+C@D z{PvH780fwZX71x1KiaZpP$*@!u{Zxd`pbr9m>zt{sM*+;VQZVD*Z4Z;P z4-2&{aqjD7mPQSF_RNe6jfrti0uOZ65l8TP_r)^@t44-ZncgJ9%4;>CkM4Jb$@fP$ z>rT=C{^Vnmbthk)h{XTj%sv^_tIxh|-Sut9@s%IC>ylu0_oLnC+=e*YT`#N5Ob^h8 zk0rX6-hG@kBT}Cg(N=HrnIRSFt?v5RN!C*enRS6eR1f{`NhG;9nw0WX4}I5}a=dd& z;Nj`9K@>p4?DBQ!38U`C+)gT|l;Dai0Zn!33F^78mwt*<#??~l3nz?v=KNmzwz_7p zm(uNa1UF!Pa$iSu==BbZYOfsF+;Y5lSzi9y=wLnnkG~c|rTJpiR=+;{2huJ-RE#d) zb55^#=@mDJn{s>aC!{y*iW1o9tWI0hM^`z6Z;GlB@EC5DsUi6kj-(h{^2wiRHO7e70obndS4wYgZyA$ zUHyU)Qa{1tzNlzt9UWVOqIXM%P7pGLB|rJs&7%-~DY zfP+1qY#l!x=aP%HKPk+ahF$c7Rp{}h19ap$On2W%RpqC&1AaBTn$qUKIaS;4()torW0D2w^!`!CD=^6F+-g!`Vh%vdvb)X0+&j(Ox*)>rrU(K5F= zPRkwd4tHPd=GoZ)qjTMV{@L*h)*I`rV4V=I;vNz13jv$-iCXwk#<#Vcyez`y9LCp?6!makcap%e3Tb14V0+)d9G1i&}Vd3tpb`zq(e?ckHP;r_ii+o3i(4P_lUsH5 zs?1>bU3TY(JeqZIbGPkI%@?PLH4g@p@~SIqC4Fl(R(ad{(6(2fb5}Vz&>M1csCmhe zD&j~^ss9J}A@9`=5}`b*e3ph?;S>>k(W%_6N=|fjWM{KIi=SdSNXRc&Mj^gynRZ(v>uT2vgOJ>7U|iw&3exIqBqpO z;^yjZG2vHSL#&gKp`Q++tFF8y*vs>O7kjm>O)hvS$Esy#u3i&jCYVQa^`Q{6#GIX{ z$JAjBX{Hy|F%!ex*E054Jm$3>4JuV;X^`uCRyyaGF#9JkbHnYraj2Ql)_u)m#Ho@h zZ@%|RDe1WtAje|&MUeX|{p(A`whIMc`OeZ4H)Op0({Y?<>=t+dBJUoq6NkJQko_ynl;15lshrU0OjrV~9{cfZg8gPP= z+OaX{+hWbSZQU1!<{o+T$hAomq8Sh_5akC%_no9w-t1Q|eBP)+Vz^3`YaRQRUa%Z_ z?H7>pV)yDjZyJ`ny&Z{?xWZ)WF_1%}%n{-4n@Q^@cFW#3Wllp<$aElubt5)JpN=x? zg}d+L98>@Jds}xjPBo3Tu?f~flceMMiO%S){UY+U(a22Ha~qm9eUDDGFUFh{_HTKH zqge?@;zoI-H}xd_Oe36RW3b*V@>G$o)EJ2+?Hc3DqxGW3lwLk>m_FQ?lezmQ(-)2; zyw+~htY4-1a`z*S*9Xf*wsn^0cklEjW(yiwt-0Ai&un70l<*vxEccTJSZ-gHnX>`Vcapz zex%?(3tUNz+}5-T`F(+-TWowi0RZUjCX!&>6p^8HKlFq7LMRk>4O*wvX| zP1hae#=)WK_T^;dzIRT_k3QVVG?JOQxP`~A#rrFZ9F&Y*<#8S?Cay0X&#!cd&Dxi>_S8Ja;sGT(6`zIl2L&^HoTd&KQ*lzp(-3C}@ z)xF%TS@IvE^EtX|^^@^trAn(Cr Onn!M$>d=0.10.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.9", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "14.2.2", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "10.3.10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.2", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.7.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.12", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.12.7", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.2.79", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.2.25", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-dom/node_modules/@types/react": { + "version": "18.2.78", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/type-utils": "7.7.0", + "@typescript-eslint/utils": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.7.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/typescript-estree": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.7.0", + "@typescript-eslint/utils": "7.7.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.7.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.7.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/visitor-keys": "7.7.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.7.0", + "@typescript-eslint/types": "7.7.0", + "@typescript-eslint/typescript-estree": "7.7.0", + "semver": "^7.6.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.7.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "dev": true, + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.11.3", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "dev": true, + "license": "MIT", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.filter": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract": { + "version": "1.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/available-typed-arrays": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/es-set-tostringtag": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/hasown": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/is-negative-zero": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/typed-array-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/typed-array-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast/node_modules/es-abstract/node_modules/typed-array-length": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.toreversed/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract": { + "version": "1.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/available-typed-arrays": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/es-set-tostringtag": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/hasown": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/is-negative-zero": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/typed-array-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/typed-array-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed/node_modules/es-abstract/node_modules/typed-array-length": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.1.0", + "es-shim-unscopables": "^1.0.2" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "dev": true, + "license": "MIT" + }, + "node_modules/asynciterator.prototype": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/attr-accept": { + "version": "2.2.2", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.7.0", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios": { + "version": "1.6.8", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axobject-query": { + "version": "3.2.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.0", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserslist/node_modules/caniuse-lite": { + "version": "1.0.30001593", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/busboy": { + "version": "1.6.0", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "set-function-length": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001600", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "license": "Apache-2.0", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/class-variance-authority/node_modules/clsx": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/debug": { + "version": "4.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.2", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.690", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.15", + "dev": true, + "license": "MIT", + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "es-set-tostringtag": "^2.0.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.0.1" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb": { + "version": "19.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5" + }, + "engines": { + "node": "^10.12.0 || ^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3", + "eslint-plugin-jsx-a11y": "^6.5.1", + "eslint-plugin-react": "^7.28.0", + "eslint-plugin-react-hooks": "^4.3.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-config-next": { + "version": "14.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "14.2.2", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": { + "version": "6.21.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/resolve": { + "version": "1.22.8", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2", + "aria-query": "^5.3.0", + "array-includes": "^3.1.7", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "=4.7.0", + "axobject-query": "^3.2.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.15", + "hasown": "^2.0.0", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.34.1", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlast": "^1.2.4", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.3", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.17", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.7", + "object.fromentries": "^2.0.7", + "object.hasown": "^1.1.3", + "object.values": "^1.1.7", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.10" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers": { + "version": "1.0.17", + "dev": true, + "license": "MIT", + "dependencies": { + "asynciterator.prototype": "^1.0.0", + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.4", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.2", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/call-bind": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract": { + "version": "1.22.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.1", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.0", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.5", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/available-typed-arrays": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/es-set-tostringtag": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/has-proto": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/hasown": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-negative-zero": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/typed-array-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/typed-array-byte-length": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/es-abstract/node_modules/typed-array-length": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/es-iterator-helpers/node_modules/has-property-descriptors": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-tailwindcss": { + "version": "3.15.1", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.5", + "postcss": "^8.4.4" + }, + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "tailwindcss": "^3.4.0" + } + }, + "node_modules/eslint-plugin-tailwindcss/node_modules/postcss": { + "version": "8.4.35", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/eslint-plugin-tailwindcss/node_modules/postcss/node_modules/source-map-js": { + "version": "1.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "dev": true, + "license": "MIT", + "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.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-selector": { + "version": "0.6.0", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.2", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "14.2.2", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.2", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.2", + "@next/swc-darwin-x64": "14.2.2", + "@next/swc-linux-arm64-gnu": "14.2.2", + "@next/swc-linux-arm64-musl": "14.2.2", + "@next/swc-linux-x64-gnu": "14.2.2", + "@next/swc-linux-x64-musl": "14.2.2", + "@next/swc-win32-arm64-msvc": "14.2.2", + "@next/swc-win32-ia32-msvc": "14.2.2", + "@next/swc-win32-x64-msvc": "14.2.2" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/next/node_modules/postcss/node_modules/source-map-js": { + "version": "1.0.2", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/node": { + "version": "20.18.1", + "resolved": "https://registry.npmjs.org/node/-/node-20.18.1.tgz", + "integrity": "sha512-WLHDiy+rNdeNVcKh2Z8a0PVvGfPUyclVUjClNN2+E7encesc8t9LwMNdfV1WCdX+ZyHaZdEe6GbyoxAqoeWnDw==", + "hasInstallScript": true, + "dependencies": { + "node-bin-setup": "^1.0.0" + }, + "bin": { + "node": "bin/node" + }, + "engines": { + "npm": ">=5.0.0" + } + }, + "node_modules/node-bin-setup": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/node-bin-setup/-/node-bin-setup-1.1.3.tgz", + "integrity": "sha512-opgw9iSCAzT2+6wJOETCpeRYAQxSopqQ2z+N6BXwIMsQQ7Zj5M8MaafQY8JMlolRR6R1UXg2WmhKp0p9lSOivg==" + }, + "node_modules/node-releases": { + "version": "2.0.14", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.filter": "^1.0.3", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0" + } + }, + "node_modules/object.hasown": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-import/node_modules/resolve": { + "version": "1.22.8", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.2.0", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.0.0", + "get-intrinsic": "^1.2.3", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "get-intrinsic": "^1.2.2", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.0", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.2", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string-width": { + "version": "4.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "regexp.prototype.flags": "^1.5.0", + "set-function-name": "^2.0.0", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tailwind-merge": { + "version": "2.2.2", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-merge/node_modules/@babel/runtime": { + "version": "7.24.1", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "dev": true, + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tailwindcss/node_modules/resolve": { + "version": "1.22.8", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.14", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.6", + "call-bind": "^1.0.5", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.3.4", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.2.tgz", + "integrity": "sha512-3iPgMhzbalizGwHNFUcGnDhFPSgVBHQ8aqSTAMxB5BvJG0oYrDf1WOJZlbXBgunOEj/8KMVbejEur/FpvFsgFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.2.tgz", + "integrity": "sha512-zbfPtkk7L41ODMJwSp5VbmPozPmMMQrzAc0HAUomVeVIIwlDGs/UCqLJvLNDt4jpWgc21SjjyIn762lNGrMaUA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.2.tgz", + "integrity": "sha512-wPbS3pI/JU16rm3XdLvvTmlsmm1nd+sBa2ohXgBZcShX4TgOjD4R+RqHKlI1cjo/jDZKXt6OxmcU0Iys0OC/yg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.2.tgz", + "integrity": "sha512-NqWOHqqq8iC9tuHvZxjQ2tX+jWy2X9y8NX2mcB4sj2bIccuCxbIZrU/ThFPZZPauygajZuVQ6zediejQHwZHwQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.2.tgz", + "integrity": "sha512-lGepHhwb9sGhCcU7999+iK1ZZT+6rrIoVg40MP7DZski9GIZP80wORSbt5kJzh9v2x2ev2lxC6VgwMQT0PcgTA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.2.tgz", + "integrity": "sha512-TZSh/48SfcLEQ4rD25VVn2kdIgUWmMflRX3OiyPwGNXn3NiyPqhqei/BaqCYXViIQ+6QsG9R0C8LftMqy8JPMA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.2.tgz", + "integrity": "sha512-M0tBVNMEBJN2ZNQWlcekMn6pvLria7Sa2Fai5znm7CCJz4pP3lrvlSxhKdkCerk0D9E0bqx5yAo3o2Q7RrD4gA==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.2.tgz", + "integrity": "sha512-a/20E/wtTJZ3Ykv3f/8F0l7TtgQa2LWHU2oNB9bsu0VjqGuGGHmm/q6waoUNQYTVPYrrlxxaHjJcDV6aiSTt/w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json index d0a1497..a97ba53 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "class-variance-authority": "0.7.0", "clsx": "2.1.0", "next": "14.2.2", + "node": "^20.18.1", "react": "18.2.0", "react-dom": "18.2.0", "react-dropzone": "14.2.3", diff --git a/frontend/src/components/ChooseFileStep/index.tsx b/frontend/src/components/ChooseFileStep/index.tsx index 5f7ad26..edeefa7 100644 --- a/frontend/src/components/ChooseFileStep/index.tsx +++ b/frontend/src/components/ChooseFileStep/index.tsx @@ -1,12 +1,21 @@ -import { FC, useCallback } from 'react'; +import { FC } from 'react'; import { useDropzone } from 'react-dropzone'; import UploadIcon from '@/icons/UploadIcon'; type ChooseFileStepProps = { + onFileSelect: (file: File) => void; }; -export const ChooseFileStep: FC = () => { +export const ChooseFileStep: FC = ({ onFileSelect }) => { const { getRootProps, getInputProps } = useDropzone({ + accept: { + 'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx'], + }, + onDrop: (acceptedFiles) => { + if (acceptedFiles.length > 0) { + onFileSelect(acceptedFiles[0]); + } + }, }); return ( @@ -22,7 +31,7 @@ export const ChooseFileStep: FC = () => {

- Drag and drop a PowerPoint file to convert to PDF. + Drag and drop a PowerPoint file to convert.

); }; - -type CompressionSelectBoxProps = { - checked?: boolean; - value: string; - description: string; -}; - -const SelectBox: FC = ({ - checked = true, - value, - description, -}) => ( -); - -const Loader = () => ( -
-
-

Compressing your file...

-
-); diff --git a/frontend/src/components/DownloadFileStep/index.tsx b/frontend/src/components/DownloadFileStep/index.tsx index fd2ffe1..34ae9c3 100644 --- a/frontend/src/components/DownloadFileStep/index.tsx +++ b/frontend/src/components/DownloadFileStep/index.tsx @@ -3,14 +3,14 @@ import {PdfIcon} from "@/icons/PdfIcon"; import {CheckIcon} from "@/icons/CheckIcon"; type DownloadFileStepProps = { - // TODO: Add the required props. + videoUrls: string[]; + onConvertAgain: () => void; }; -export const DownloadFileStep: FC = () => { - const result = 'https://google.com'; - const onConvertAgain = () => { - } - +export const DownloadFileStep: FC = ({ + videoUrls, + onConvertAgain, +}) => { return (
@@ -18,7 +18,7 @@ export const DownloadFileStep: FC = () => {
-

File converted successfully!

+

Videos extracted successfully!

- - Download file - + +
); diff --git a/frontend/src/components/PowerPointToPdfConverter/index.tsx b/frontend/src/components/PowerPointToPdfConverter/index.tsx index 538e2b1..d204a83 100644 --- a/frontend/src/components/PowerPointToPdfConverter/index.tsx +++ b/frontend/src/components/PowerPointToPdfConverter/index.tsx @@ -1,8 +1,44 @@ 'use client'; -import {useState} from "react"; -import {ChooseFileStep} from "@/components/ChooseFileStep"; +import { FC, useState } from 'react'; +import { ChooseFileStep } from '@/components/ChooseFileStep'; +import { ConvertFileStep } from '@/components/ConvertFileStep'; +import { DownloadFileStep } from '@/components/DownloadFileStep'; -export const PowerPointToPdfConverter = () => { - return -} \ No newline at end of file +export const PowerPointToPdfConverter: FC = () => { + const [file, setFile] = useState(null); + const [videoUrls, setVideoUrls] = useState([]); + const [currentStep, setCurrentStep] = useState<'choose' | 'convert' | 'download'>('choose'); + + const reset = () => { + setFile(null); + setVideoUrls([]); + setCurrentStep('choose'); + }; + + const handleFileSelect = (selectedFile: File) => { + setFile(selectedFile); + setCurrentStep('convert'); + }; + + const handleConversionComplete = (urls: string[]) => { + setVideoUrls(urls); + setCurrentStep('download'); + }; + + return ( + <> + {currentStep === 'choose' && } + {currentStep === 'convert' && file && ( + + )} + {currentStep === 'download' && ( + + )} + + ); +}; From a1c362674a4593a4b1007168f0dd1ce47f45b730 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Sat, 23 Nov 2024 16:11:49 +0100 Subject: [PATCH 13/25] chore: Added CORS settings to allow frontend endpoint call - Removed unused REDIS_PORT env --- backend/.env.example | 3 +-- backend/app/main.py | 12 +++++++++++- backend/app/video_extractor.py | 3 --- backend/docker-compose.yml | 1 - 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index b073baa..67a9c87 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,5 +1,4 @@ AWS_ACCESS_KEY= AWS_SECRET_KEY= S3_BUCKET_NAME= -AWS_REGION= -REDIS_PORT= \ No newline at end of file +AWS_REGION= \ No newline at end of file diff --git a/backend/app/main.py b/backend/app/main.py index 8ee5bf0..ad4f986 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,9 +1,19 @@ -from fastapi import FastAPI, UploadFile, HTTPException, BackgroundTasks +from fastapi import FastAPI, UploadFile, HTTPException +from fastapi.middleware.cors import CORSMiddleware from video_extractor import extract_videos_task, convert_pptx_with_unzip import os app = FastAPI() +# Allow CORS for the frontend +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000"], # Allowed origins + allow_credentials=True, + allow_methods=["*"], # Allowed HTTP methods + allow_headers=["*"], # Allowed headers +) + @app.post("/extract") async def extract_videos(file: UploadFile): # if file.content_type != "application/vnd.openxmlformats-officedocument.presentationml.presentation": diff --git a/backend/app/video_extractor.py b/backend/app/video_extractor.py index 05190cb..7fa53e0 100644 --- a/backend/app/video_extractor.py +++ b/backend/app/video_extractor.py @@ -4,9 +4,6 @@ import boto3 from botocore.exceptions import NoCredentialsError -# Redis Configuration -REDIS_PORT = os.getenv("REDIS_PORT") - # Setup Celery celery = Celery( "extract_videos_task", diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 53565eb..0f6d2be 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.9" services: backend: build: . From 5305d08d92b6ddaea562719ca1a38b0f067e8d2c Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Sat, 23 Nov 2024 16:21:20 +0100 Subject: [PATCH 14/25] chore: Fixed typo and improoved videos list style --- .../src/components/ConvertFileStep/index.tsx | 2 +- .../src/components/DownloadFileStep/index.tsx | 21 ++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/ConvertFileStep/index.tsx b/frontend/src/components/ConvertFileStep/index.tsx index ea5f5eb..ef0cbc0 100644 --- a/frontend/src/components/ConvertFileStep/index.tsx +++ b/frontend/src/components/ConvertFileStep/index.tsx @@ -29,8 +29,8 @@ export const ConvertFileStep: FC = ({ }); const result = await response.json(); - if (response.ok) { + onConversionComplete(result.task.urls); } else { setErrorMessage(result.detail || 'An error occurred during conversion.'); diff --git a/frontend/src/components/DownloadFileStep/index.tsx b/frontend/src/components/DownloadFileStep/index.tsx index 34ae9c3..62a7de2 100644 --- a/frontend/src/components/DownloadFileStep/index.tsx +++ b/frontend/src/components/DownloadFileStep/index.tsx @@ -23,21 +23,28 @@ export const DownloadFileStep: FC = ({
- +
); From 592482716a93d6959331401089e38f426096b5f8 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Sat, 23 Nov 2024 16:41:37 +0100 Subject: [PATCH 15/25] chore: Replace PDF icon by VideoIcon --- frontend/src/components/DownloadFileStep/index.tsx | 6 +++--- frontend/src/icons/PdfIcon/index.tsx | 12 ------------ frontend/src/icons/VideoIcon/index.tsx | 5 +++++ 3 files changed, 8 insertions(+), 15 deletions(-) delete mode 100644 frontend/src/icons/PdfIcon/index.tsx create mode 100644 frontend/src/icons/VideoIcon/index.tsx diff --git a/frontend/src/components/DownloadFileStep/index.tsx b/frontend/src/components/DownloadFileStep/index.tsx index 62a7de2..8125af3 100644 --- a/frontend/src/components/DownloadFileStep/index.tsx +++ b/frontend/src/components/DownloadFileStep/index.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import {PdfIcon} from "@/icons/PdfIcon"; +import {VideoIcon} from "@/icons/VideoIcon"; import {CheckIcon} from "@/icons/CheckIcon"; type DownloadFileStepProps = { @@ -14,8 +14,8 @@ export const DownloadFileStep: FC = ({ return (
- -
+ +

Videos extracted successfully!

diff --git a/frontend/src/icons/PdfIcon/index.tsx b/frontend/src/icons/PdfIcon/index.tsx deleted file mode 100644 index 963a416..0000000 --- a/frontend/src/icons/PdfIcon/index.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export const PdfIcon = () => ( - - - - - -) \ No newline at end of file diff --git a/frontend/src/icons/VideoIcon/index.tsx b/frontend/src/icons/VideoIcon/index.tsx new file mode 100644 index 0000000..656281e --- /dev/null +++ b/frontend/src/icons/VideoIcon/index.tsx @@ -0,0 +1,5 @@ +export const VideoIcon = () => ( + +) + +export default VideoIcon; From 07fbdbe4436d1fa67cef726bb6e09d86c87fcd99 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Sat, 23 Nov 2024 17:03:16 +0100 Subject: [PATCH 16/25] docs: Added CORS section to README --- backend/README.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/README.md b/backend/README.md index 0618ae5..8674c1b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -79,7 +79,6 @@ backend/ β”‚ β”œβ”€β”€ __init__.py β”‚ β”œβ”€β”€ main.py # Entry point for FastAPI app β”‚ β”œβ”€β”€ tasks.py # Logic for video extraction and S3 upload -β”‚ β”œβ”€β”€ celery_worker.py # Celery worker setup β”œβ”€β”€ requirements.txt β”œβ”€β”€ Dockerfile β”œβ”€β”€ docker-compose.yml @@ -95,4 +94,23 @@ backend/ - python-multipart: Required by FastAPI to handle file uploads. - celery: Task queue system for parallel processing. - redis: Backend for Celery and message broker. -- aiofiles: Asynchronous file I/O for FastAPI when saving uploaded files \ No newline at end of file +- aiofiles: Asynchronous file I/O for FastAPI when saving uploaded files + + +### CORS + +This is the current CORS setup: + +```py +# main.py + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000"], # Allowed origins + allow_credentials=True, + allow_methods=["*"], # Allowed HTTP methods + allow_headers=["*"], # Allowed headers +) +``` + +If you want to run in production or test it in your local network, you will need to change the configuration accordingly \ No newline at end of file From d217cc65c6f478bdcd9a4e647e5fa0ca72df9aa9 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 3 Dec 2024 16:18:57 +0100 Subject: [PATCH 17/25] feat(SLI-91): add celery worker to docker compose (#8) * chore: Added a celery container worker and dockerfile * docs: Added Celery explanation to the backend README * chore: add env to celery * chore: Enable docker for production & readme `make` instructions * fix: correct production entry point (cmd) for celery --- backend/Dev.Dockerfile | 16 ++++++++ backend/Dev.Dockerfile.celery | 24 +++++++++++ backend/Dockerfile | 4 +- backend/Dockerfile.celery | 24 +++++++++++ backend/README.md | 71 +++++++++++++++++++++++++++++---- backend/docker-compose.prod.yml | 41 +++++++++++++++++++ backend/docker-compose.yml | 33 ++++++++++++++- backend/makefile | 17 ++++++++ backend/requirements-dev.txt | 2 + 9 files changed, 220 insertions(+), 12 deletions(-) create mode 100644 backend/Dev.Dockerfile create mode 100644 backend/Dev.Dockerfile.celery create mode 100644 backend/Dockerfile.celery create mode 100644 backend/docker-compose.prod.yml create mode 100644 backend/makefile create mode 100644 backend/requirements-dev.txt diff --git a/backend/Dev.Dockerfile b/backend/Dev.Dockerfile new file mode 100644 index 0000000..39845d3 --- /dev/null +++ b/backend/Dev.Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.9-slim + +WORKDIR /app + +# Install system dependencies (unzip) +RUN apt-get update && apt-get install -y unzip + +# Install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY ./app /app + +# Run the FastAPI app +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/backend/Dev.Dockerfile.celery b/backend/Dev.Dockerfile.celery new file mode 100644 index 0000000..e7dd819 --- /dev/null +++ b/backend/Dev.Dockerfile.celery @@ -0,0 +1,24 @@ +FROM python:3.9-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + unzip \ + gcc \ + libffi-dev \ + musl-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Copy & Install Python dependencies +COPY requirements.txt requirements.txt +COPY requirements-dev.txt requirements-dev.txt +RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --no-cache-dir -r requirements-dev.txt + +# Copy application code +COPY ./app /app + +# Run Celery worker with watchdog +CMD ["watchmedo", "auto-restart", "-d", ".", "-R", "-p", "*.py", "--debug-force-polling", "--", "celery", "-A", "video_extractor.celery", "worker", "--loglevel=info", "-c", "16"] diff --git a/backend/Dockerfile b/backend/Dockerfile index 6badfe2..acb80d8 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,10 +7,10 @@ RUN apt-get update && apt-get install -y unzip # Install Python dependencies COPY requirements.txt . -RUN pip install -r requirements.txt +RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY ./app /app # Run the FastAPI app -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"] \ No newline at end of file diff --git a/backend/Dockerfile.celery b/backend/Dockerfile.celery new file mode 100644 index 0000000..299c5a6 --- /dev/null +++ b/backend/Dockerfile.celery @@ -0,0 +1,24 @@ +# Base image +FROM python:3.9-slim + +# Set working directory +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + unzip \ + gcc \ + libffi-dev \ + musl-dev \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies +COPY requirements.txt requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY ./app /app + +# Set the default command to run Celery worker +CMD ["celery", "-A", "video_extractor.celery", "worker", "--loglevel=info", "-c", "16"] diff --git a/backend/README.md b/backend/README.md index 8674c1b..9e0795b 100644 --- a/backend/README.md +++ b/backend/README.md @@ -2,11 +2,61 @@ - Having Docker installed on your system. -## Running the docker container - -```bash -docker compose up --build -``` +## Running the docker containers + +The project includes a `Makefile` to simplify common Docker Compose tasks. You can use the following commands to manage your development and production environments: + +### Available Commands + +| Command | Description | +|---------------|---------------------------------------------------------------------------------------------| +| `make start` | Starts the development environment using `docker-compose.yml`. | +| `make build` | Builds the Docker images and starts the development environment. | +| `make detach` | Builds the Docker images, starts the development environment in detached mode (background). | +| `make down` | Stops and removes all containers, networks, and volumes defined in `docker-compose.yml`. | +| `make prod` | Starts the production environment using `docker-compose.prod.yml`. | +| `make prod-down` | Stops and removes all containers, networks, and volumes defined in `docker-compose.prod.yml`. | + +--- + +### How to Use + +1. **Start Development Environment**: + - Run the following command to start your development environment: + ```bash + make start + ``` + - This command will spin up the containers as defined in `docker-compose.yml`. + +2. **Build and Start Containers**: + - If you’ve made changes to your Dockerfile or dependencies, rebuild the containers with: + ```bash + make build + ``` + +3. **Run in Detached Mode**: + - To run the containers in the background, use: + ```bash + make detach + ``` + +4. **Shut Down the Environment**: + - To stop and clean up all containers, networks, and volumes, run: + ```bash + make down + ``` + +5. **Start Production Environment**: + - Use this command to start the production environment defined in `docker-compose.prod.yml`: + ```bash + make prod + ``` + +6. **Shut Down Production Environment**: + - To stop and clean up the production containers, use: + ```bash + make prod-down + ``` *** @@ -50,6 +100,13 @@ As seen here: https://github.com/SlideSpeak/image-extractor-cli/blob/30c5ad96ffb *** +### Celery + +Celery is running on a docker container. +You can use celery by implementing the decorator on the function you wish to queue. +Then you should use `.delay` to trigger the celery queue which will return the task id. +After that you can query the celery worker for the result, if any. + ### Coding Now we know what the python script should look like. @@ -61,9 +118,7 @@ Fast API route: - file: file.pptx ##### NOTE: - I am happy :D !!! - I learnt something new! - How to make a test with postman when a post requires a named file parameter. + How to make a test with postman when a post requires a named file parameter? You should go to postman and change the request to a post. Then on the Body section choose form-data. Then hover on the `Key` section and choose "File" on the dropdown. diff --git a/backend/docker-compose.prod.yml b/backend/docker-compose.prod.yml new file mode 100644 index 0000000..84213ad --- /dev/null +++ b/backend/docker-compose.prod.yml @@ -0,0 +1,41 @@ +services: + backend: + build: + context: . + dockerfile: Dockerfile + container_name: backend_prod + ports: + - "8000:8000" + depends_on: + - redis + volumes: + - ./app:/app + - shared_tmp:/shared_tmp + env_file: + - path: ".env" + required: true + restart: always + + redis: + image: redis:6.2 + container_name: redis_prod + ports: + - "6379:6379" + + celery_worker: + build: + context: . + dockerfile: Dockerfile.celery + container_name: celery_worker_prod + command: > + celery -A video_extractor.celery worker --loglevel=info -c 16 + volumes: + - shared_tmp:/shared_tmp + env_file: + - path: ".env" + required: true + depends_on: + - redis + +volumes: + shared_tmp: \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 0f6d2be..59f4323 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,12 +1,16 @@ services: backend: - build: . + build: + context: . + dockerfile: Dev.Dockerfile + container_name: backend_dev ports: - "8000:8000" depends_on: - redis volumes: - ./app:/app + - shared_tmp:/shared_tmp env_file: - path: ".env" required: true @@ -14,5 +18,30 @@ services: redis: image: redis:6.2 + container_name: redis_dev ports: - - "6342:6379" + - "6379:6379" + + celery_worker: + build: + context: . + dockerfile: Dev.Dockerfile.celery + container_name: celery_worker_dev + command: > + watchmedo auto-restart + -d . + -R + -p '*.py' + --debug-force-polling + -- celery -A video_extractor.celery worker --loglevel=info -c 16 + volumes: + - ./app:/app # For development you do want to share this volume to get notified when the app changes + - shared_tmp:/shared_tmp + env_file: + - path: ".env" + required: true + depends_on: + - redis + +volumes: + shared_tmp: \ No newline at end of file diff --git a/backend/makefile b/backend/makefile new file mode 100644 index 0000000..63f0867 --- /dev/null +++ b/backend/makefile @@ -0,0 +1,17 @@ +start: + docker compose -f docker-compose.yml up + +build: + docker compose -f docker-compose.yml up --build + +detach: + docker compose -f docker-compose.yml up --build -d + +down: + docker compose -f docker-compose.yml down + +prod: + docker compose -f docker-compose.prod.yml up --build + +prod-down: + docker compose -f docker-compose.prod.yml down \ No newline at end of file diff --git a/backend/requirements-dev.txt b/backend/requirements-dev.txt new file mode 100644 index 0000000..a105327 --- /dev/null +++ b/backend/requirements-dev.txt @@ -0,0 +1,2 @@ +# Development-specific requirements +watchdog[watchmedo]==6.0.0 From c8290379bf5a6738692ae7d48aabdfdfdab84442 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 3 Dec 2024 16:22:29 +0100 Subject: [PATCH 18/25] fix(sli-90): add celery queuing (#9) * chore: add config, setup celery decorator parameters * chore: add GET get-result/ to retrive the task result --- backend/.env.example | 4 +++- backend/app/config.py | 25 +++++++++++++++++++++++++ backend/app/main.py | 28 ++++++++++++++++++++-------- backend/app/video_extractor.py | 29 ++++++++++++++++------------- 4 files changed, 64 insertions(+), 22 deletions(-) create mode 100644 backend/app/config.py diff --git a/backend/.env.example b/backend/.env.example index 67a9c87..334cfde 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,6 @@ AWS_ACCESS_KEY= AWS_SECRET_KEY= S3_BUCKET_NAME= -AWS_REGION= \ No newline at end of file +AWS_REGION= +CELERY_BROKER_URL=redis://redis:6379/0 +CELERY_BACKEND_URL=redis://redis:6379/0 \ No newline at end of file diff --git a/backend/app/config.py b/backend/app/config.py new file mode 100644 index 0000000..b8cd8db --- /dev/null +++ b/backend/app/config.py @@ -0,0 +1,25 @@ +import os + +# S3 Configuration +AWS_ACCESS_KEY = os.getenv("AWS_ACCESS_KEY") +AWS_SECRET_KEY = os.getenv("AWS_SECRET_KEY") +S3_BUCKET_NAME = os.getenv("S3_BUCKET_NAME") +AWS_REGION = os.getenv("AWS_REGION") + +# Celery Configuration +CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "redis://redis:6379/0") +CELERY_BACKEND_URL = os.getenv("CELERY_BACKEND_URL", "redis://redis:6379/0") + +# Local Directories +LOCAL_DOCUMENTS_DIR = "shared_tmp" + +# Task Settings +MAX_CONVERT_TRIES = 5 +SOFT_TIME_LIMIT = 120 +TIME_LIMIT = 300 + +# CORS Settings +ALLOWED_ORIGINS = [ + "https://slidespeak.co", + "http://localhost:3000", +] diff --git a/backend/app/main.py b/backend/app/main.py index ad4f986..2b62a9b 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,6 +1,8 @@ from fastapi import FastAPI, UploadFile, HTTPException from fastapi.middleware.cors import CORSMiddleware -from video_extractor import extract_videos_task, convert_pptx_with_unzip +from video_extractor import extract_videos_task, celery +from config import ALLOWED_ORIGINS, LOCAL_DOCUMENTS_DIR +from celery.result import AsyncResult import os app = FastAPI() @@ -8,7 +10,7 @@ # Allow CORS for the frontend app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:3000"], # Allowed origins + allow_origins=ALLOWED_ORIGINS, # Allowed origins allow_credentials=True, allow_methods=["*"], # Allowed HTTP methods allow_headers=["*"], # Allowed headers @@ -21,15 +23,25 @@ async def extract_videos(file: UploadFile): raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PowerPoint file.") # Save file temporarily - temp_path = f"/tmp/{file.filename}" + temp_path = f"/{LOCAL_DOCUMENTS_DIR}/{file.filename}" with open(temp_path, "wb") as f: content = await file.read() f.write(content) # Queue the extraction task - videos = extract_videos_task(temp_path) + task = extract_videos_task.delay(temp_path) + + return {"task_id": str(task.id), "status": "Task sent to Celery worker"} + +@app.get("/get-result/{task_id}") +async def get_result(task_id: str): + # Retrieve result from celery + task = AsyncResult(task_id, app=celery) + + if task.ready(): + task_result = task.result + task.forget() + return {"status": "SUCCESS", "result": task_result} + else: + return {"status": "PENDING", "result": None} - # Clean up temporary files and directories - if os.path.exists(temp_path): - os.remove(temp_path) - return {"task": videos} diff --git a/backend/app/video_extractor.py b/backend/app/video_extractor.py index 7fa53e0..3848d1c 100644 --- a/backend/app/video_extractor.py +++ b/backend/app/video_extractor.py @@ -1,23 +1,22 @@ import os import subprocess -from celery import Celery import boto3 +from celery import Celery from botocore.exceptions import NoCredentialsError +from config import ( + AWS_ACCESS_KEY, AWS_SECRET_KEY, S3_BUCKET_NAME, AWS_REGION, + CELERY_BROKER_URL, CELERY_BACKEND_URL, + SOFT_TIME_LIMIT, TIME_LIMIT, MAX_CONVERT_TRIES +) # Setup Celery celery = Celery( - "extract_videos_task", - backend=f"redis://redis:6379/0", - broker="redis://redis:6379/0", + "video_extractor", + broker=CELERY_BROKER_URL, + backend=CELERY_BACKEND_URL, ) -# S3 Configuration -AWS_ACCESS_KEY = os.getenv("AWS_ACCESS_KEY") -AWS_SECRET_KEY = os.getenv("AWS_SECRET_KEY") -S3_BUCKET_NAME = os.getenv("S3_BUCKET_NAME") -AWS_REGION = os.getenv("AWS_REGION") - # AWS S3 Client s3_client = boto3.client( "s3", @@ -85,7 +84,12 @@ def upload_to_s3(file_path, s3_key): raise RuntimeError(f"Error uploading to S3: {e}") -@celery.task +@celery.task( + soft_time_limit=SOFT_TIME_LIMIT, + time_limit=TIME_LIMIT, + max_retries=MAX_CONVERT_TRIES, + priority=10, +) def extract_videos_task(file_path): """ Celery task to extract videos from a PowerPoint file. @@ -100,7 +104,6 @@ def extract_videos_task(file_path): # Step 2: Extract videos from the converted directory extracted_videos = extract_videos_from_directory(output_dir) - print("Extracted Videos", extracted_videos) if not extracted_videos: return {"message": "No videos found in the presentation.", "urls": []} @@ -114,6 +117,6 @@ def extract_videos_task(file_path): # Make sure path does not exist after job is finished if os.path.exists(output_dir): - subprocess.run(["rm", "-rf", output_dir], check=False) + subprocess.run(["rm", "-rf", output_dir, file_path], check=False) return {"message": "Videos extracted and uploaded successfully.", "urls": presigned_urls} From 7b43885a664e4bdf50c4c8b296aa22616937f046 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 3 Dec 2024 16:33:34 +0100 Subject: [PATCH 19/25] feat(SLI-89): add robust try-except blocks (#10) * chore: add robust try-catch-finally blocks & cleanup guarantee * feat: implement os.remove and shutil.rmtree instead of subprocess --- backend/app/video_extractor.py | 54 ++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/backend/app/video_extractor.py b/backend/app/video_extractor.py index 3848d1c..9b10517 100644 --- a/backend/app/video_extractor.py +++ b/backend/app/video_extractor.py @@ -8,6 +8,7 @@ CELERY_BROKER_URL, CELERY_BACKEND_URL, SOFT_TIME_LIMIT, TIME_LIMIT, MAX_CONVERT_TRIES ) +import shutil # Setup Celery celery = Celery( @@ -64,6 +65,9 @@ def extract_videos_from_directory(input_dir): if os.path.splitext(file)[1].lower() in video_extensions: videos.append(os.path.join(input_dir, file)) + if not videos: + raise RuntimeError("No video files found in the extracted directory.") + return videos @@ -95,28 +99,42 @@ def extract_videos_task(file_path): Celery task to extract videos from a PowerPoint file. """ output_dir = f"{file_path.replace(' ', '')}_output" - # Make sure path does not exist, could exist if an error occured and didnt cleanup - if os.path.exists(output_dir): + + try: + # Make sure path does not exist, could exist if an error occured and didnt cleanup + if os.path.exists(output_dir): subprocess.run(["rm", "-rf", output_dir], check=False) - # Step 1: Convert the PowerPoint file to a ZIP-like structure using Unzip - convert_pptx_with_unzip(file_path, output_dir) + # Step 1: Convert the PowerPoint file to a ZIP-like structure using Unzip + convert_pptx_with_unzip(file_path, output_dir) + + # Step 2: Extract videos from the converted directory + extracted_videos = extract_videos_from_directory(output_dir) - # Step 2: Extract videos from the converted directory - extracted_videos = extract_videos_from_directory(output_dir) - if not extracted_videos: - return {"message": "No videos found in the presentation.", "urls": []} + if not extracted_videos: + return {"message": "No videos found in the presentation.", "urls": []} - # # Step 3: Upload videos to S3 and generate presigned URLs - presigned_urls = [] - for video in extracted_videos: - s3_key = f"videos/{os.path.basename(video)}" - presigned_url = upload_to_s3(video, s3_key) - presigned_urls.append(presigned_url) + # # Step 3: Upload videos to S3 and generate presigned URLs + presigned_urls = [] + for video in extracted_videos: + s3_key = f"videos/{os.path.basename(video)}" + presigned_url = upload_to_s3(video, s3_key) + presigned_urls.append(presigned_url) - # Make sure path does not exist after job is finished - if os.path.exists(output_dir): - subprocess.run(["rm", "-rf", output_dir, file_path], check=False) + return {"message": "Videos extracted and uploaded successfully.", "urls": presigned_urls} - return {"message": "Videos extracted and uploaded successfully.", "urls": presigned_urls} + except RuntimeError as e: + # Return a descriptive error message if a known error occurs + return {"error": str(e)} + + except Exception as e: + # Catch unexpected errors + return {"error": f"An unexpected error occurred: {str(e)}"} + + finally: + # Cleanup temporary files + if os.path.exists(output_dir): + shutil.rmtree(output_dir) + if os.path.exists(file_path): + os.remove(file_path) From 7093ff7954d7e2430c9744d7534a2c2d0389855a Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 3 Dec 2024 18:06:00 +0100 Subject: [PATCH 20/25] feat(SLI-88) (#11) * feat: generate .zip file when multiple media videos are found * chore: ensure .zip is deleted as well --- backend/README.md | 33 ++++++++++++++++- backend/app/video_extractor.py | 67 ++++++++++++++++++++-------------- 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/backend/README.md b/backend/README.md index 9e0795b..a7f9dec 100644 --- a/backend/README.md +++ b/backend/README.md @@ -152,6 +152,37 @@ backend/ - aiofiles: Asynchronous file I/O for FastAPI when saving uploaded files +## Zipping Videos for S3 Upload + +When multiple video files are extracted from the PowerPoint presentation, the project uses Python's `shutil.make_archive()` to create a compressed ZIP file for efficient storage and upload to S3. + +### How It Works + +1. **Single Video**: + - If only one video is found, it is uploaded directly to S3 without compression. + - The S3 link for the video is returned. + +2. **Multiple Videos**: + - If multiple videos are found, they are compressed into a single ZIP file using `shutil.make_archive()` before being uploaded to S3. + - The S3 link for the ZIP file is returned. + +3. **ZIP Creation**: + - The `shutil.make_archive()` function creates a standard ZIP file that is fully compatible with ZIP tools (e.g., Windows File Explorer, macOS Finder, `unzip` command). + - Compression ensures reduced file size for faster uploads and downloads. + +### Code Example + +Here’s how the ZIP archive is created in the project: + +```python +shutil.make_archive(zip_path.replace(".zip", ""), "zip", output_dir) +``` + +- **`zip_path.replace(".zip", "")`**: Defines the name of the ZIP file without the `.zip` extension (automatically added by the function). +- **`"zip"`**: Specifies the archive format (ZIP in this case). +- **`output_dir`**: The directory containing the extracted video files to include in the ZIP. + + ### CORS This is the current CORS setup: @@ -161,7 +192,7 @@ This is the current CORS setup: app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:3000"], # Allowed origins + allow_origins=[], # Allowed origins allow_credentials=True, allow_methods=["*"], # Allowed HTTP methods allow_headers=["*"], # Allowed headers diff --git a/backend/app/video_extractor.py b/backend/app/video_extractor.py index 9b10517..c2297d9 100644 --- a/backend/app/video_extractor.py +++ b/backend/app/video_extractor.py @@ -30,6 +30,10 @@ def convert_pptx_with_unzip(pptx_file_path, output_dir): """ Convert the .pptx file to a raw directory structure using Unzip. + + Args: + pptx_file_path (str): Path to the pptx file. + output_dir (str): Path to the output directory (*_output) """ os.makedirs(output_dir, exist_ok=True) unzip_command = [ @@ -50,30 +54,26 @@ def convert_pptx_with_unzip(pptx_file_path, output_dir): raise RuntimeError(f"Unzip didnt provide the output file: {output_dir}") -def extract_videos_from_directory(input_dir): - """ - Extract video files from the converted directory. +def filter_files_by_extension(output_dir, extensions): """ - video_extensions = {".mp4", ".mov", ".avi", ".mkv"} - videos = [] - - # Look for media files in the `ppt/media/` directory - if not os.path.exists(input_dir): - raise RuntimeError(f"Unusual pptx format, no {input_dir} founded") - - for file in os.listdir(input_dir): - if os.path.splitext(file)[1].lower() in video_extensions: - videos.append(os.path.join(input_dir, file)) + Filters files in the given directory, keeping only those with specified extensions. - if not videos: - raise RuntimeError("No video files found in the extracted directory.") - - return videos + Args: + output_dir (str): Path to the directory to filter. + extensions (set): A set of file extensions to keep (e.g., {".mp4", ".mov"}). + """ + for file in os.listdir(output_dir): + if not any(file.lower().endswith(ext) for ext in extensions): + os.remove(os.path.join(output_dir, file)) def upload_to_s3(file_path, s3_key): """ Upload a file to S3 and generate a presigned URL. + + Args: + file_path (str): The file to be uploadedr. + s3_key (set): The s3 key, where it will live in the bucket. """ try: s3_client.upload_file(file_path, S3_BUCKET_NAME, s3_key) @@ -97,8 +97,12 @@ def upload_to_s3(file_path, s3_key): def extract_videos_task(file_path): """ Celery task to extract videos from a PowerPoint file. + + Args: + file_path (str): Path to the downloaded pptx file. """ output_dir = f"{file_path.replace(' ', '')}_output" + zip_path = f"{file_path.replace(' ', '')}.zip" try: # Make sure path does not exist, could exist if an error occured and didnt cleanup @@ -109,20 +113,27 @@ def extract_videos_task(file_path): convert_pptx_with_unzip(file_path, output_dir) # Step 2: Extract videos from the converted directory - extracted_videos = extract_videos_from_directory(output_dir) + video_extensions = {".mp4", ".mov", ".avi", ".mkv"} + filter_files_by_extension(output_dir, video_extensions) + extracted_files = os.listdir(output_dir) + if not extracted_files: + raise RuntimeError("No video files were found in the uploaded presentation.") - if not extracted_videos: - return {"message": "No videos found in the presentation.", "urls": []} + if len(extracted_files) == 1: + # Only one video file, upload it directly + file_to_upload = os.path.join(output_dir, extracted_files[0]) + s3_key = f"videos/{os.path.basename(file_to_upload)}" + else: + # Multiple files, zip the directory + shutil.make_archive(zip_path.replace(".zip", ""), "zip", output_dir) + file_to_upload = zip_path + s3_key = f"videos/{os.path.basename(zip_path)}" - # # Step 3: Upload videos to S3 and generate presigned URLs - presigned_urls = [] - for video in extracted_videos: - s3_key = f"videos/{os.path.basename(video)}" - presigned_url = upload_to_s3(video, s3_key) - presigned_urls.append(presigned_url) + # Step 3: Upload videos to S3 and generate presigned URLs + presigned_url = upload_to_s3(file_to_upload, s3_key) - return {"message": "Videos extracted and uploaded successfully.", "urls": presigned_urls} + return {"message": "Videos extracted and uploaded successfully.", "url": presigned_url} except RuntimeError as e: # Return a descriptive error message if a known error occurs @@ -138,3 +149,5 @@ def extract_videos_task(file_path): shutil.rmtree(output_dir) if os.path.exists(file_path): os.remove(file_path) + if os.path.exists(zip_path): + os.remove(zip_path) From f367d8b7271eb910ea084b8644c0e2dbba8e3130 Mon Sep 17 00:00:00 2001 From: Andre Date: Tue, 3 Dec 2024 18:21:49 +0100 Subject: [PATCH 21/25] chore: implement uuid4() for unique file names (#12) --- backend/app/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/app/main.py b/backend/app/main.py index 2b62a9b..f8143fb 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -4,6 +4,7 @@ from config import ALLOWED_ORIGINS, LOCAL_DOCUMENTS_DIR from celery.result import AsyncResult import os +import uuid app = FastAPI() @@ -23,7 +24,7 @@ async def extract_videos(file: UploadFile): raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PowerPoint file.") # Save file temporarily - temp_path = f"/{LOCAL_DOCUMENTS_DIR}/{file.filename}" + temp_path = f"/{LOCAL_DOCUMENTS_DIR}/{uuid.uuid4()}_{file.filename}" with open(temp_path, "wb") as f: content = await file.read() f.write(content) From 3f132bfac90901510dc3e925ad53cc49ab1fb5a3 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Tue, 3 Dec 2024 18:26:58 +0100 Subject: [PATCH 22/25] chore: add versions to requirements.txt pip packages --- backend/requirements.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 7b6c448..74325be 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -1,7 +1,7 @@ -fastapi -uvicorn -boto3 -python-multipart -celery -redis -aiofiles +fastapi==0.115.5 +uvicorn==0.32.1 +boto3==1.35.73 +python-multipart==0.0.19 +celery==5.4.0 +redis==5.2.0 +aiofiles==24.1.0 From c29fec0647e1e14e282832b07b9c6904603126a3 Mon Sep 17 00:00:00 2001 From: pulgamecanica Date: Tue, 3 Dec 2024 19:46:27 +0100 Subject: [PATCH 23/25] feat(SLI-85): conversion step with loading and disable states --- .../src/components/ConvertFileStep/index.tsx | 95 ++++++++++++++++++- .../src/components/DownloadFileStep/index.tsx | 32 +++---- .../PowerPointToPdfConverter/index.tsx | 10 +- frontend/src/icons/VideoIcon/index.tsx | 23 ++++- 4 files changed, 132 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/ConvertFileStep/index.tsx b/frontend/src/components/ConvertFileStep/index.tsx index ef0cbc0..0837ad5 100644 --- a/frontend/src/components/ConvertFileStep/index.tsx +++ b/frontend/src/components/ConvertFileStep/index.tsx @@ -1,10 +1,11 @@ import { FC, useState } from 'react'; import { LoadingIndicatorIcon } from '@/icons/LoadingIndicatorIcon'; +import { cn } from "@/utils/cn"; type ConvertFileStepProps = { file: File; onCancel: () => void; - onConversionComplete: (videoUrls: string[]) => void; + onConversionComplete: (videoUrl: string) => void; }; export const ConvertFileStep: FC = ({ @@ -30,8 +31,8 @@ export const ConvertFileStep: FC = ({ const result = await response.json(); if (response.ok) { - - onConversionComplete(result.task.urls); + const taskId = result.task_id; + await pollForResult(taskId); } else { setErrorMessage(result.detail || 'An error occurred during conversion.'); } @@ -42,6 +43,41 @@ export const ConvertFileStep: FC = ({ } }; + const pollForResult = async (taskId: string) => { + let retries = 0; + const maxRetries = 10; + + const poll = async () => { + try { + const response = await fetch(`http://localhost:8000/get-result/${taskId}`); + const result = await response.json(); + + if (response.ok) { + if (result.status === 'SUCCESS') { + if (result.result.error) { + setErrorMessage(result.result.error || 'Conversion failed.'); + } else if (result.result.url) { + onConversionComplete(result.result.url); + } + return; + } + } + + retries++; + if (retries < maxRetries) { + setTimeout(poll, 1000); // Retry after 1 second + } else { + setErrorMessage('Conversion timed out. Please try again.'); + } + } catch (error) { + setErrorMessage('Error fetching conversion result.'); + } + }; + + poll(); + setIsConverting(false); + }; + const formatBytes = (bytes: number, decimals = 2) => { if (!+bytes) return '0 Bytes'; const k = 1024; @@ -57,12 +93,24 @@ export const ConvertFileStep: FC = ({

{file.name}

{formatBytes(file.size)}

+ + {isConverting ? ( + + ) : ( + + )} + {errorMessage &&

{errorMessage}

} +
@@ -78,3 +126,44 @@ export const ConvertFileStep: FC = ({
); }; + +const Loader = () => ( +
+
+

Extracting the videos...

+
+); + +type ConversionSelectBoxProps = { + checked?: boolean; + value: string; + description: string; +}; + +const ConversionSelectBox: FC = ({ + checked = true, + value, + description, +}) => ( +