From eff090d82580cc7bbc64833c270936e16646369b Mon Sep 17 00:00:00 2001 From: Aditya Chandel <8075870+adityachandelgit@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:12:44 -0700 Subject: [PATCH 01/46] Update github templates (#1840) --- .github/ISSUE_TEMPLATE/bug_report.md | 39 +- .github/ISSUE_TEMPLATE/feature_request.md | 17 +- .../pull_request_template.md | 39 ++ CONTRIBUTING.md | 396 ++++++++++++++---- 4 files changed, 358 insertions(+), 133 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index fa06dffc..e8f255bf 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,38 +1,24 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' +# ๐Ÿ› Bug Report ---- - -# ๐Ÿ› Bug Report for Booklore - -Thank you for taking the time to report this bug. Your feedback helps make Booklore better for everyone! - -Let's squash this bug together! ๐Ÿ”จ - ---- - -## ๐Ÿ“ What happened? +## ๐Ÿ“ Description ## ๐Ÿ”„ Steps to Reproduce -1. -2. -3. -4. +1. +2. +3. +4. **Result:** + ## โœ… Expected Behavior -## ๐Ÿ“ธ Screenshots / Error Messages +## ๐Ÿ“ธ Screenshots / Error Messages _(Optional)_ @@ -44,9 +30,10 @@ ## ๐Ÿ’ป Environment - **Installation:** (e.g., Docker, Unraid, Manual) - **Storage Type:** (e.g., Local HDD/SSD, Synology NAS, SMB Share, NFS Mount, S3 Bucket) -## ๐Ÿ“Œ Additional Context - - -## โœจ Possible Solution _(Optional)_ +## ๐Ÿ’ก Possible Solution _(Optional)_ + + +## ๐Ÿ“Œ Additional Context _(Optional)_ + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index cd886513..53260300 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,17 +1,4 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - ---- - -# โœจ Feature Request for Booklore - -Thank you for contributing to Booklore's development. Your suggestions help shape the future of this project. - ---- +# โœจ Feature Request ## ๐Ÿ“ Description @@ -36,5 +23,5 @@ ## ๐ŸŽจ Technical Details _(Optional)_ -## ๐Ÿ“Œ Additional Context +## ๐Ÿ“Œ Additional Context _(Optional)_ diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 00000000..9290de9f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,39 @@ +# ๐Ÿš€ Pull Request + +## ๐Ÿ“ Description + + + + +## ๐Ÿ› ๏ธ What's Changed? + + +- + + +## ๐Ÿงช How Was This Tested? + + +- [ ] Manual testing on my local machine +- [ ] Added new unit/integration tests +- [ ] All existing tests pass + + +## ๐Ÿ“ธ Screenshots / Videos _(Optional)_ + + + + +## โœ… Checklist + +- [ ] I have read the **CONTRIBUTING** document. +- [ ] My code follows the project's coding standards. +- [ ] I have added/updated tests to cover my changes. +- [ ] I have updated the documentation if necessary. +- [ ] I have run `./gradlew test` successfully (for backend changes). +- [ ] My branch is up-to-date with the `main` branch. +- [ ] **For big features:** I have created a documentation PR at [booklore-docs](https://github.com/booklore-app/booklore-docs) with styling similar to other documentation pages. + + +## ๐Ÿ“Œ Additional Context _(Optional)_ + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40f7ad7f..a3cb080d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,17 @@ # Contributing to Booklore -๐ŸŽ‰ Thanks for your interest in contributing to **Booklore**, a modern, self-hostable digital library system for books and comics. Whether you're fixing bugs, adding features, improving documentation, or asking questions - your contribution matters! +๐ŸŽ‰ **Thank you for your interest in contributing to Booklore!** Whether you're fixing bugs, adding features, improving documentation, or simply asking questions, every contribution helps make Booklore better for everyone. --- -## ๐Ÿ“š Overview +## ๐Ÿ“š What is Booklore? -**Booklore** is a self-hostable platform designed to manage and read books and comics. It includes: +**Booklore** is a modern, self-hostable digital library platform for managing and reading books and comics. It's designed with privacy, flexibility, and ease of use in mind. -- **Frontend**: Angular 20, TypeScript, PrimeNG 19, Tailwind CSS +**Tech Stack:** +- **Frontend**: Angular 20, TypeScript, PrimeNG 19 - **Backend**: Java 21, Spring Boot 3.5 -- **Authentication**: Local JWT + optional OIDC (e.g. Authentik) +- **Authentication**: Local JWT + optional OIDC (e.g., Authentik) - **Database**: MariaDB - **Deployment**: Docker-compatible, reverse proxy-ready @@ -20,116 +21,202 @@ ## ๐Ÿ“ฆ Project Structure ``` booklore/ -โ”œโ”€โ”€ booklore-ui/ # Angular frontend -โ”œโ”€โ”€ booklore-api/ # Spring Boot backend -โ”œโ”€โ”€ assets/ # Shared assets +โ”œโ”€โ”€ booklore-ui/ # Angular frontend application +โ”œโ”€โ”€ booklore-api/ # Spring Boot backend API +โ”œโ”€โ”€ assets/ # Shared assets (logos, icons, etc.) +โ”œโ”€โ”€ docker-compose.yml # Production Docker setup +โ””โ”€โ”€ dev.docker-compose.yml # Development Docker setup ``` --- ## ๐Ÿš€ Getting Started -1. **Fork the repository** on GitHub -2. **Clone your fork** locally: +### 1. Fork and Clone + +First, fork the repository to your GitHub account, then clone it locally: ```bash -git clone https://github.com/adityachandelgit/BookLore.git +# Clone your fork +git clone https://github.com//booklore.git cd booklore + +# Add upstream remote to keep your fork in sync +git remote add upstream https://github.com/booklore-app/booklore.git +``` + +### 2. Keep Your Fork Updated + +Before starting work on a new feature or fix: + +```bash +# Fetch latest changes from upstream +git fetch upstream + +# Merge upstream changes into your local main branch +git checkout main +git merge upstream/main + +# Push updates to your fork +git push origin main ``` --- ## ๐Ÿงฑ Local Development Setup -Booklore has a simple all-in-one Docker development stack, or you can install & run everything manually. +Booklore offers two development approaches: an all-in-one Docker stack for quick setup, or manual installation for more control. +### Option 1: Docker Development Stack (Recommended for Quick Start) -### Development using Docker stack - -Run `docker compose -f dev.docker-compose.yml up` - -- Dev web server is accessible at `http://localhost:4200/` -- Dev database is accessible at `http://localhost:3366/` -- Remote java debugging is accessible at `http://localhost:5005/` - -All ports are configurable using environment variables - see dev.docker-compose.yml - ---- - -### Development on local machine - -#### 1. Prerequisites - -- **Java 21+** -- **Node.js 18+** -- **MariaDB** -- **Docker and Docker Compose** - ---- - -#### 2. Frontend Setup - -To set up the Angular frontend: +This option sets up everything with a single command: ```bash -cd booklore-ui -npm install -ng serve +docker compose -f dev.docker-compose.yml up ``` -The dev server runs at `http://localhost:4200/`. +**What you get:** +- โœ… Frontend dev server at `http://localhost:4200/` +- โœ… Backend API at `http://localhost:8080/` +- โœ… MariaDB at `localhost:3366` +- โœ… Remote Java debugging at `localhost:5005` -> โš ๏ธ Use `--force` with `npm install` only as a last resort for dependency conflicts. +**Note:** All ports are configurable via environment variables in `dev.docker-compose.yml`: +- `FRONTEND_PORT` (default: 4200) +- `BACKEND_PORT` (default: 8080) +- `DB_PORT` (default: 3366) +- `REMOTE_DEBUG_PORT` (default: 5005) + +**Stopping the stack:** +```bash +docker compose -f dev.docker-compose.yml down +``` --- -#### 3. Backend Setup +### Option 2: Manual Local Development -##### a. Configure `application-dev.yml` +For more control over your development environment, you can run each component separately. -Create or edit `booklore-api/src/main/resources/application-dev.yml`: +#### Prerequisites + +Ensure you have the following installed: +- **Java 21+** ([Download](https://adoptium.net/)) +- **Node.js 18+** and **npm** ([Download](https://nodejs.org/)) +- **MariaDB 10.6+** ([Download](https://mariadb.org/download/)) +- **Git** ([Download](https://git-scm.com/)) + +#### Frontend Setup + +```bash +# Navigate to the frontend directory +cd booklore-ui + +# Install dependencies +npm install + +# Start the development server +ng serve + +# Or use npm script +npm start +``` + +The frontend will be available at `http://localhost:4200/` with hot-reload enabled. + +**Common Issues:** +- If you encounter dependency conflicts, try `npm install --legacy-peer-deps` +- Use `--force` only as a last resort + +--- + +#### Backend Setup + +##### Step 1: Configure Application Properties + +Create a development configuration file at `booklore-api/src/main/resources/application-dev.yml`: ```yaml app: - path-book: '/path/to/booklore/books' # Directory for book/comic files - path-config: '/path/to/booklore/config' # Directory for thumbnails, metadata, etc. + # Path where books and comics are stored + path-book: '/Users/yourname/booklore-data/books' + + # Path for thumbnails, metadata cache, and other config files + path-config: '/Users/yourname/booklore-data/config' spring: datasource: driver-class-name: org.mariadb.jdbc.Driver - url: jdbc:mariadb://localhost:3333/booklore?createDatabaseIfNotExist=true + url: jdbc:mariadb://localhost:3306/booklore?createDatabaseIfNotExist=true username: root - password: Password123 + password: your_secure_password ``` -> ๐Ÿ”ง Replace `/path/to/...` with actual local paths +**Important:** +- Replace `/Users/yourname/...` with actual paths on your system +- Create these directories if they don't exist +- Ensure proper read/write permissions -##### b. Run the Backend +**Example paths:** +- **macOS/Linux**: `/Users/yourname/booklore-data/books` +- **Windows**: `C:\Users\yourname\booklore-data\books` + +##### Step 2: Set Up the Database + +Ensure MariaDB is running and create the database: + +```bash +# Connect to MariaDB +mysql -u root -p + +# Create database and user (optional) +CREATE DATABASE IF NOT EXISTS booklore; +CREATE USER 'booklore_user'@'localhost' IDENTIFIED BY 'your_secure_password'; +GRANT ALL PRIVILEGES ON booklore.* TO 'booklore_user'@'localhost'; +FLUSH PRIVILEGES; +EXIT; +``` + +##### Step 3: Run the Backend ```bash cd booklore-api -./gradlew bootRun +./gradlew bootRun --args='--spring.profiles.active=dev' +``` + +The backend API will be available at `http://localhost:8080/` + +**Verify it's running:** +```bash +curl http://localhost:8080/actuator/health ``` --- ## ๐Ÿงช Testing -### Frontend +Always run tests before submitting a pull request to ensure your changes don't break existing functionality. -Run unit tests using: - -```bash -cd booklore-ui -ng test -``` - -### Backend - -Run backend tests using: +### Backend Tests ```bash cd booklore-api + +# Run all tests +./gradlew test + +# Run tests with detailed output +./gradlew test --info + +# Run a specific test class +./gradlew test --tests "com.booklore.api.service.BookServiceTest" + +# Generate coverage report +./gradlew test jacocoTestReport +``` + +**Before creating a PR, always run:** +```bash ./gradlew test ``` @@ -137,74 +224,199 @@ ### Backend ## ๐Ÿ› ๏ธ Contributing Guidelines -### ๐Ÿ’ก Bug Reports +### ๐Ÿ’ก Reporting Bugs -- Check [existing issues](https://github.com/adityachandelgit/BookLore/issues) -- Include reproduction steps, expected vs. actual behavior, and logs if possible +Found a bug? Help us fix it by providing detailed information: -### ๐ŸŒŸ Feature Requests +1. **Search existing issues** to avoid duplicates +2. **Create a new issue** with the `bug` label +3. **Include the following:** + - Clear, descriptive title (e.g., "Book import fails with PDF files over 100MB") + - Steps to reproduce the issue + - Expected behavior vs. actual behavior + - Screenshots or error logs if applicable + - Your environment (OS, browser, Docker version, etc.) -- Clearly explain the use case and benefit -- Label the issue with `feature` +**Example Bug Report:** +```markdown +**Title:** Book metadata not updating after manual edit -### ๐Ÿ”ƒ Code Contributions +**Description:** +When I manually edit a book's metadata through the UI and click Save, +the changes appear to save but revert after page refresh. -- Create a feature branch: +**Steps to Reproduce:** +1. Navigate to any book detail page +2. Click "Edit Metadata" +3. Change the title from "Old Title" to "New Title" +4. Click "Save" +5. Refresh the page -```bash -git checkout -b feat/my-feature +**Expected:** Title should remain "New Title" +**Actual:** Title reverts to "Old Title" + +**Environment:** +- Browser: Chrome 120 +- OS: macOS 14.2 +- Booklore Version: 1.2.0 ``` -- For bug fixes: +--- + +### ๐Ÿ”ƒ Submitting Code Changes + +#### Branch Naming Convention + +Create descriptive branches that clearly indicate the purpose of your changes: ```bash -git checkout -b fix/my-fix +# For new features +git checkout -b feat/add-dark-mode-theme +git checkout -b feat/epub-reader-support + +# For bug fixes +git checkout -b fix/book-import-validation +git checkout -b fix/memory-leak-in-scanner + +# For documentation +git checkout -b docs/update-installation-guide + +# For refactoring +git checkout -b refactor/improve-authentication-flow ``` -- Follow code conventions, keep PRs focused and scoped -- Link the relevant issue in your PR -- Test your changes -- Target the `develop` branch when opening PRs +#### Development Workflow + +1. **Create a branch** from `develop` (not `main`) +2. **Make your changes** in small, logical commits +3. **Test thoroughly** - run both frontend and backend tests +4. **Update documentation** if your changes affect usage +5. **Run the linter** and fix any issues +6. **Commit with clear messages** following Conventional Commits +7. **Push to your fork** +8. **Open a pull request** targeting the `develop` branch + +#### Pull Request Checklist + +Before submitting, ensure: +- [ ] Code follows project conventions +- [ ] All tests pass (`./gradlew test` for backend) +- [ ] IntelliJ linter shows no errors +- [ ] Changes are documented (README, inline comments) +- [ ] PR description clearly explains what and why +- [ ] PR is linked to a related issue (if applicable) +- [ ] Branch is up-to-date with `develop` +- [ ] **For big features:** Create a documentation PR at [booklore-docs](https://github.com/booklore-app/booklore-docs) with styling similar to other documentation pages --- ## ๐Ÿงผ Code Style & Conventions - **Angular**: Follow the [official style guide](https://angular.io/guide/styleguide) -- **Java**: Use modern features (Java 17+), clean structure -- **Format**: Use linters and Prettier where applicable -- **UI**: Use Tailwind CSS and PrimeNG components consistently +- **Java**: Use modern features (Java 21), clean structure +- **Linter**: Use IntelliJ IDEA's built-in linter for code formatting and style checks +- **UI**: Use SCSS and PrimeNG components consistently --- ## ๐Ÿ“ Commit Message Format -Follow [Conventional Commits](https://www.conventionalcommits.org/): +We follow [Conventional Commits](https://www.conventionalcommits.org/) for clear, standardized commit messages. -Examples: +### Format -- `feat: add column visibility setting to book table` -- `fix: correct metadata locking behavior` -- `docs: improve contributing instructions` +``` +(): + +[optional body] + +[optional footer] +``` + +### Types + +- `feat`: New feature +- `fix`: Bug fix +- `docs`: Documentation changes +- `style`: Code style changes (formatting, no logic change) +- `refactor`: Code refactoring +- `test`: Adding or updating tests +- `chore`: Maintenance tasks +- `perf`: Performance improvements + +### Examples + +```bash +# Feature addition +feat(reader): add keyboard navigation for page turning + +# Bug fix +fix(api): resolve memory leak in book scanning service + +# Documentation +docs(readme): add troubleshooting section for Docker setup + +# Multiple scopes +feat(api,ui): implement book collection management + +# Breaking change +feat(auth)!: migrate to OAuth 2.1 + +BREAKING CHANGE: OAuth 2.0 is no longer supported +``` --- ## ๐Ÿ™ Code of Conduct -Please be respectful, inclusive, and collaborative. Harassment, abuse, or discrimination of any kind will not be tolerated. +We're committed to providing a welcoming and inclusive environment for everyone. + +**Our Standards:** +- โœ… Be respectful and considerate +- โœ… Welcome newcomers and help them learn +- โœ… Accept constructive criticism gracefully +- โœ… Focus on what's best for the community + +**Unacceptable Behavior:** +- โŒ Harassment, trolling, or discrimination +- โŒ Personal attacks or insults +- โŒ Publishing others' private information +- โŒ Any conduct that would be inappropriate in a professional setting + +**Enforcement:** +Instances of unacceptable behavior may result in temporary or permanent ban from the project. --- ## ๐Ÿ’ฌ Community & Support -- Discord server: https://discord.gg/Ee5hd458Uz +**Need help or want to discuss ideas?** + +- ๐Ÿ’ฌ **Discord**: [Join our server](https://discord.gg/Ee5hd458Uz) +- ๐Ÿ› **Issues**: [GitHub Issues](https://github.com/adityachandelgit/BookLore/issues) --- ## ๐Ÿ“„ License -Booklore is open-source and licensed under the GPL-3.0 License. See [`LICENSE`](./LICENSE) for details. +Booklore is open-source software licensed under the **GPL-3.0 License**. + +By contributing, you agree that your contributions will be licensed under the same license. See the [`LICENSE`](./LICENSE) file for full details. --- -Happy contributing! +## ๐ŸŽฏ What to Work On? + +Not sure where to start? Check out: + +- Issues labeled [`good first issue`](https://github.com/adityachandelgit/BookLore/labels/good%20first%20issue) +- Issues labeled [`help wanted`](https://github.com/adityachandelgit/BookLore/labels/help%20wanted) +- Our [project roadmap](https://github.com/adityachandelgit/BookLore/projects) + +--- + +## ๐ŸŽ‰ Thank You! + +Every contribution, no matter how small, makes Booklore better. Thank you for being part of our community! + +**Happy Contributing! ๐Ÿ“šโœจ** From 7d631233a3cdce00cf5d9bb9bb00394829da416c Mon Sep 17 00:00:00 2001 From: "aditya.chandel" <8075870+adityachandelgit@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:16:26 -0700 Subject: [PATCH 02/46] Add missing sections to GitHub templates --- .github/ISSUE_TEMPLATE/bug_report.md | 8 ++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 8 ++++++++ .github/PULL_REQUEST_TEMPLATE/pull_request_template.md | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e8f255bf..7395e691 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,3 +1,11 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' +--- + # ๐Ÿ› Bug Report ## ๐Ÿ“ Description diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 53260300..319f3e2f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,3 +1,11 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' +--- + # โœจ Feature Request ## ๐Ÿ“ Description diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 9290de9f..783d1004 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -1,3 +1,11 @@ +--- +name: Pull Request +about: Submit a pull request for this project +title: '' +labels: '' +assignees: '' +--- + # ๐Ÿš€ Pull Request ## ๐Ÿ“ Description From 6dcc45ab4d34f57bc48b2c368ae6370614c0930b Mon Sep 17 00:00:00 2001 From: "aditya.chandel" <8075870+adityachandelgit@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:20:06 -0700 Subject: [PATCH 03/46] Relocate PR template to ensure it appears properly on GitHub --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename .github/{PULL_REQUEST_TEMPLATE => }/pull_request_template.md (100%) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 319f3e2f..bfaf52cf 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: Suggest an idea for this project title: '' -labels: enhancement +labels: '' assignees: '' --- diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/pull_request_template.md similarity index 100% rename from .github/PULL_REQUEST_TEMPLATE/pull_request_template.md rename to .github/pull_request_template.md From b175007d7f9500d3c0ccdda40d82de843be0b781 Mon Sep 17 00:00:00 2001 From: "aditya.chandel" <8075870+adityachandelgit@users.noreply.github.com> Date: Fri, 12 Dec 2025 16:27:49 -0700 Subject: [PATCH 04/46] Update PR template --- .github/pull_request_template.md | 52 ++++++++++++-------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 783d1004..5d915d67 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,47 +1,35 @@ ---- -name: Pull Request -about: Submit a pull request for this project -title: '' -labels: '' -assignees: '' ---- - # ๐Ÿš€ Pull Request ## ๐Ÿ“ Description - - + + -## ๐Ÿ› ๏ธ What's Changed? - - +## ๐Ÿ› ๏ธ Changes Made + - -## ๐Ÿงช How Was This Tested? - - -- [ ] Manual testing on my local machine -- [ ] Added new unit/integration tests +## ๐Ÿงช Testing + +- [ ] Tested manually in local environment +- [ ] Added/updated automated unit/integration tests - [ ] All existing tests pass -## ๐Ÿ“ธ Screenshots / Videos _(Optional)_ - - +## ๐Ÿ“ธ Visual Changes _(if applicable)_ + -## โœ… Checklist - -- [ ] I have read the **CONTRIBUTING** document. -- [ ] My code follows the project's coding standards. -- [ ] I have added/updated tests to cover my changes. -- [ ] I have updated the documentation if necessary. -- [ ] I have run `./gradlew test` successfully (for backend changes). -- [ ] My branch is up-to-date with the `main` branch. -- [ ] **For big features:** I have created a documentation PR at [booklore-docs](https://github.com/booklore-app/booklore-docs) with styling similar to other documentation pages. +## โœ… Pre-submission Checklist + +- [ ] Code follows project style guidelines and conventions +- [ ] Tests added/updated to cover changes +- [ ] All tests pass locally (`./gradlew test` for backend) +- [ ] Flyway migration versioning is correct _(if database changes made)_ +- [ ] Branch is synced with latest `develop` branch +- [ ] Documentation PR created at [booklore-docs](https://github.com/booklore-app/booklore-docs) _(for major features)_ -## ๐Ÿ“Œ Additional Context _(Optional)_ - +## ๐Ÿ’ฌ Additional Notes _(optional)_ + From b55b684125fdf7bd3b923d27b9e86d6fd0b5f69b Mon Sep 17 00:00:00 2001 From: "aditya.chandel" <8075870+adityachandelgit@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:15:28 -0700 Subject: [PATCH 05/46] Update PR template --- .github/pull_request_template.md | 36 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 5d915d67..5e394a91 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,35 +1,35 @@ # ๐Ÿš€ Pull Request ## ๐Ÿ“ Description - + -## ๐Ÿ› ๏ธ Changes Made - +## ๐Ÿ› ๏ธ Changes Implemented + - -## ๐Ÿงช Testing - -- [ ] Tested manually in local environment -- [ ] Added/updated automated unit/integration tests -- [ ] All existing tests pass +## ๐Ÿงช Testing Strategy + + ## ๐Ÿ“ธ Visual Changes _(if applicable)_ - + -## โœ… Pre-submission Checklist - -- [ ] Code follows project style guidelines and conventions -- [ ] Tests added/updated to cover changes +## โš ๏ธ Required Pre-Submission Checklist + + +- [ ] Code adheres to project style guidelines and conventions +- [ ] Branch synchronized with latest `develop` branch +- [ ] Automated unit/integration tests added/updated to cover changes - [ ] All tests pass locally (`./gradlew test` for backend) -- [ ] Flyway migration versioning is correct _(if database changes made)_ -- [ ] Branch is synced with latest `develop` branch -- [ ] Documentation PR created at [booklore-docs](https://github.com/booklore-app/booklore-docs) _(for major features)_ +- [ ] Manual testing completed in local development environment +- [ ] Flyway migration versioning follows correct sequence _(if database schema modified)_ +- [ ] Documentation pull request submitted to [booklore-docs](https://github.com/booklore-app/booklore-docs) _(required for features or enhancements that introduce user-facing or visual changes)_ -## ๐Ÿ’ฌ Additional Notes _(optional)_ - +## ๐Ÿ’ฌ Additional Context _(optional)_ + From b64c30f3bc087d183bff30686c753b089dab5329 Mon Sep 17 00:00:00 2001 From: WorldTeacher <41587052+WorldTeacher@users.noreply.github.com> Date: Sat, 13 Dec 2025 23:46:59 +0100 Subject: [PATCH 06/46] feat(opds): add series hierarchy (#1837) Co-authored-by: WorldTeacher --- .../booklore/controller/OpdsController.java | 10 +++ .../repository/BookOpdsRepository.java | 48 ++++++++++++++ .../service/opds/OpdsBookService.java | 58 ++++++++++++++++- .../service/opds/OpdsFeedService.java | 64 +++++++++++++++++-- 4 files changed, 174 insertions(+), 6 deletions(-) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/OpdsController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/OpdsController.java index 107bc59a..bd1a11f5 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/controller/OpdsController.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/OpdsController.java @@ -107,6 +107,16 @@ public class OpdsController { .body(feed); } + @Operation(summary = "Get OPDS series navigation", description = "Retrieve the OPDS series navigation feed.") + @ApiResponse(responseCode = "200", description = "Series navigation feed returned successfully") + @GetMapping(value = "/series", produces = OPDS_CATALOG_MEDIA_TYPE) + public ResponseEntity getSeriesNavigation(@Parameter(hidden = true) HttpServletRequest request) { + String feed = opdsFeedService.generateSeriesNavigation(request); + return ResponseEntity.ok() + .contentType(MediaType.parseMediaType(OPDS_CATALOG_MEDIA_TYPE)) + .body(feed); + } + @Operation(summary = "Get OPDS catalog feed", description = "Retrieve the OPDS acquisition catalog feed.") @ApiResponse(responseCode = "200", description = "Catalog feed returned successfully") @GetMapping(value = "/catalog", produces = OPDS_ACQUISITION_MEDIA_TYPE) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/repository/BookOpdsRepository.java b/booklore-api/src/main/java/com/adityachandel/booklore/repository/BookOpdsRepository.java index 8dd9fcb9..4b93afa3 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/repository/BookOpdsRepository.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/repository/BookOpdsRepository.java @@ -162,4 +162,52 @@ public interface BookOpdsRepository extends JpaRepository, Jpa ORDER BY b.addedOn DESC """) Page findBookIdsByAuthorNameAndLibraryIds(@Param("authorName") String authorName, @Param("libraryIds") Collection libraryIds, Pageable pageable); + + // ============================================ + // SERIES - Distinct Series List + // ============================================ + + @Query(""" + SELECT DISTINCT m.seriesName FROM BookMetadataEntity m + JOIN m.book b + WHERE (b.deleted IS NULL OR b.deleted = false) + AND m.seriesName IS NOT NULL + AND m.seriesName != '' + ORDER BY m.seriesName + """) + List findDistinctSeries(); + + @Query(""" + SELECT DISTINCT m.seriesName FROM BookMetadataEntity m + JOIN m.book b + WHERE (b.deleted IS NULL OR b.deleted = false) + AND b.library.id IN :libraryIds + AND m.seriesName IS NOT NULL + AND m.seriesName != '' + ORDER BY m.seriesName + """) + List findDistinctSeriesByLibraryIds(@Param("libraryIds") Collection libraryIds); + + // ============================================ + // BOOKS BY SERIES - Two Query Pattern (sorted by series number) + // ============================================ + + @Query(""" + SELECT DISTINCT b.id FROM BookEntity b + JOIN b.metadata m + WHERE m.seriesName = :seriesName + AND (b.deleted IS NULL OR b.deleted = false) + ORDER BY COALESCE(m.seriesNumber, 999999), b.addedOn DESC + """) + Page findBookIdsBySeriesName(@Param("seriesName") String seriesName, Pageable pageable); + + @Query(""" + SELECT DISTINCT b.id FROM BookEntity b + JOIN b.metadata m + WHERE m.seriesName = :seriesName + AND b.library.id IN :libraryIds + AND (b.deleted IS NULL OR b.deleted = false) + ORDER BY COALESCE(m.seriesNumber, 999999), b.addedOn DESC + """) + Page findBookIdsBySeriesNameAndLibraryIds(@Param("seriesName") String seriesName, @Param("libraryIds") Collection libraryIds, Pageable pageable); } \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsBookService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsBookService.java index 6a9c05fc..ea39f70c 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsBookService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsBookService.java @@ -202,7 +202,7 @@ public class OpdsBookService { if (idPage.isEmpty()) { return new PageImpl<>(List.of(), pageable, 0); } - List books = bookOpdsRepository.findAllWithMetadataByIds(idPage.getContent()); + List books = bookOpdsRepository.findAllWithFullMetadataByIds(idPage.getContent()); return createPageFromEntities(books, idPage, pageable); } @@ -215,7 +215,61 @@ public class OpdsBookService { return new PageImpl<>(List.of(), pageable, 0); } - List books = bookOpdsRepository.findAllWithMetadataByIdsAndLibraryIds(idPage.getContent(), libraryIds); + List books = bookOpdsRepository.findAllWithFullMetadataByIdsAndLibraryIds(idPage.getContent(), libraryIds); + Page booksPage = createPageFromEntities(books, idPage, pageable); + return applyBookFilters(booksPage, userId); + } + + public List getDistinctSeries(Long userId) { + if (userId == null) { + return List.of(); + } + + BookLoreUserEntity entity = userRepository.findById(userId) + .orElseThrow(() -> ApiError.USER_NOT_FOUND.createException(userId)); + BookLoreUser user = bookLoreUserTransformer.toDTO(entity); + + if (user.getPermissions().isAdmin()) { + return bookOpdsRepository.findDistinctSeries(); + } + + Set libraryIds = user.getAssignedLibraries().stream() + .map(Library::getId) + .collect(Collectors.toSet()); + + return bookOpdsRepository.findDistinctSeriesByLibraryIds(libraryIds); + } + + public Page getBooksBySeriesName(Long userId, String seriesName, int page, int size) { + if (userId == null) { + throw ApiError.FORBIDDEN.createException("Authentication required"); + } + + BookLoreUserEntity entity = userRepository.findById(userId) + .orElseThrow(() -> ApiError.USER_NOT_FOUND.createException(userId)); + BookLoreUser user = bookLoreUserTransformer.toDTO(entity); + + Pageable pageable = PageRequest.of(Math.max(page, 0), size); + + if (user.getPermissions().isAdmin()) { + Page idPage = bookOpdsRepository.findBookIdsBySeriesName(seriesName, pageable); + if (idPage.isEmpty()) { + return new PageImpl<>(List.of(), pageable, 0); + } + List books = bookOpdsRepository.findAllWithFullMetadataByIds(idPage.getContent()); + return createPageFromEntities(books, idPage, pageable); + } + + Set libraryIds = user.getAssignedLibraries().stream() + .map(Library::getId) + .collect(Collectors.toSet()); + + Page idPage = bookOpdsRepository.findBookIdsBySeriesNameAndLibraryIds(seriesName, libraryIds, pageable); + if (idPage.isEmpty()) { + return new PageImpl<>(List.of(), pageable, 0); + } + + List books = bookOpdsRepository.findAllWithFullMetadataByIdsAndLibraryIds(idPage.getContent(), libraryIds); Page booksPage = createPageFromEntities(books, idPage, pageable); return applyBookFilters(booksPage, userId); } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsFeedService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsFeedService.java index 80f2ed43..3372cbce 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsFeedService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsFeedService.java @@ -100,6 +100,16 @@ public class OpdsFeedService { """.formatted(now())); + feed.append(""" + + Series + urn:booklore:navigation:series + %s + + Browse books by series + + """.formatted(now())); + feed.append(""" Surprise Me @@ -270,12 +280,50 @@ public class OpdsFeedService { return feed.toString(); } + public String generateSeriesNavigation(HttpServletRequest request) { + Long userId = getUserId(); + List seriesList = opdsBookService.getDistinctSeries(userId); + + var feed = new StringBuilder(""" + + + urn:booklore:navigation:series + Series + %s + + + + """.formatted(now())); + + for (String series : seriesList) { + feed.append(""" + + %s + urn:booklore:series:%s + %s + + Books in the %s series + + """.formatted( + escapeXml(series), + escapeXml(series), + now(), + escapeXml("/api/v1/opds/catalog?series=" + java.net.URLEncoder.encode(series, java.nio.charset.StandardCharsets.UTF_8)), + escapeXml(series) + )); + } + + feed.append(""); + return feed.toString(); + } + public String generateCatalogFeed(HttpServletRequest request) { Long libraryId = parseLongParam(request, "libraryId", null); Long shelfId = parseLongParam(request, "shelfId", null); Long magicShelfId = parseLongParam(request, "magicShelfId", null); String query = request.getParameter("q"); String author = request.getParameter("author"); + String series = request.getParameter("series"); int page = Math.max(1, parseLongParam(request, "page", 1L).intValue()); int size = Math.min(parseLongParam(request, "size", (long) DEFAULT_PAGE_SIZE).intValue(), MAX_PAGE_SIZE); @@ -286,12 +334,14 @@ public class OpdsFeedService { booksPage = magicShelfBookService.getBooksByMagicShelfId(userId, magicShelfId, page - 1, size); } else if (author != null && !author.isBlank()) { booksPage = opdsBookService.getBooksByAuthorName(userId, author, page - 1, size); + } else if (series != null && !series.isBlank()) { + booksPage = opdsBookService.getBooksBySeriesName(userId, series, page - 1, size); } else { booksPage = opdsBookService.getBooksPage(userId, query, libraryId, shelfId, page - 1, size); } - String feedTitle = determineFeedTitle(libraryId, shelfId, magicShelfId, author); - String feedId = determineFeedId(libraryId, shelfId, magicShelfId, author); + String feedTitle = determineFeedTitle(libraryId, shelfId, magicShelfId, author, series); + String feedId = determineFeedId(libraryId, shelfId, magicShelfId, author, series); var feed = new StringBuilder(""" @@ -502,7 +552,7 @@ public class OpdsFeedService { } } - private String determineFeedTitle(Long libraryId, Long shelfId, Long magicShelfId, String author) { + private String determineFeedTitle(Long libraryId, Long shelfId, Long magicShelfId, String author, String series) { if (magicShelfId != null) { return magicShelfBookService.getMagicShelfName(magicShelfId); } @@ -515,10 +565,13 @@ public class OpdsFeedService { if (author != null && !author.isBlank()) { return "Books by " + author; } + if (series != null && !series.isBlank()) { + return series + " series"; + } return "Booklore Catalog"; } - private String determineFeedId(Long libraryId, Long shelfId, Long magicShelfId, String author) { + private String determineFeedId(Long libraryId, Long shelfId, Long magicShelfId, String author, String series) { if (magicShelfId != null) { return "urn:booklore:magic-shelf:" + magicShelfId; } @@ -531,6 +584,9 @@ public class OpdsFeedService { if (author != null && !author.isBlank()) { return "urn:booklore:author:" + author; } + if (series != null && !series.isBlank()) { + return "urn:booklore:series:" + series; + } return "urn:booklore:catalog"; } From 0486a4f0700249ec2c1d2319b44543c754f09291 Mon Sep 17 00:00:00 2001 From: Muppetteer Date: Sun, 14 Dec 2025 15:08:43 +1100 Subject: [PATCH 07/46] fix: Consistent dialogs (#1842) * fix/consistent-dialogs * fix: enforce consistent mobile dialog width * fix: cover search dialog image size --- .../book-browser/BookDialogHelperService.ts | 158 +++++++------ .../book-browser/book-browser.component.ts | 2 +- .../book-card/book-card.component.ts | 59 +---- .../book-table/book-table.component.ts | 2 - .../series-page/series-page.component.ts | 2 +- .../shelf-assigner.component.ts | 2 +- .../service/library-shelf-menu.service.ts | 55 +---- .../bookdrop-file-review.component.ts | 13 +- .../dashboard-settings.component.scss | 4 +- .../main-dashboard.component.ts | 10 +- .../library-creator.component.ts | 19 +- .../metadata-editor.component.scss | 4 + .../metadata-editor.component.ts | 21 +- .../metadata-viewer.component.ts | 50 +--- .../metadata-review-dialog-component.html | 5 - .../email-v2-provider.component.ts | 15 +- .../email-v2-recipient.component.ts | 15 +- .../user-management.component.ts | 14 +- .../metadata-progress-widget-component.ts | 14 +- .../layout-menu/app.menu.component.ts | 21 +- .../layout-menu/app.menuitem.component.ts | 6 +- .../layout-topbar/app.topbar.component.ts | 5 +- .../app/shared/service/icon-picker.service.ts | 20 +- .../services/dialog-launcher.service.ts | 222 +++++++++++++----- .../src/assets/layout/styles/global.scss | 15 ++ 25 files changed, 333 insertions(+), 420 deletions(-) diff --git a/booklore-ui/src/app/features/book/components/book-browser/BookDialogHelperService.ts b/booklore-ui/src/app/features/book/components/book-browser/BookDialogHelperService.ts index ad8700cc..8ad16f61 100644 --- a/booklore-ui/src/app/features/book/components/book-browser/BookDialogHelperService.ts +++ b/booklore-ui/src/app/features/book/components/book-browser/BookDialogHelperService.ts @@ -1,5 +1,6 @@ import {inject, Injectable} from '@angular/core'; -import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {DynamicDialogRef} from 'primeng/dynamicdialog'; +import {DialogLauncherService} from '../../../../shared/services/dialog-launcher.service'; import {ShelfAssignerComponent} from '../shelf-assigner/shelf-assigner.component'; import {LockUnlockMetadataDialogComponent} from './lock-unlock-metadata-dialog/lock-unlock-metadata-dialog.component'; import {MetadataRefreshType} from '../../../metadata/model/request/metadata-refresh-type.enum'; @@ -8,50 +9,61 @@ import {MultiBookMetadataEditorComponent} from '../../../metadata/component/mult import {MultiBookMetadataFetchComponent} from '../../../metadata/component/multi-book-metadata-fetch/multi-book-metadata-fetch-component'; import {FileMoverComponent} from '../../../../shared/components/file-mover/file-mover-component'; import {ShelfCreatorComponent} from '../shelf-creator/shelf-creator.component'; +import {BookSenderComponent} from '../book-sender/book-sender.component'; +import {MetadataFetchOptionsComponent} from '../../../metadata/component/metadata-options-dialog/metadata-fetch-options/metadata-fetch-options.component'; +import {BookMetadataCenterComponent} from '../../../metadata/component/book-metadata-center/book-metadata-center.component'; +import {CoverSearchComponent} from '../../../metadata/component/cover-search/cover-search.component'; +import {Book} from '../../model/book.model'; +import {AdditionalFileUploaderComponent} from '../additional-file-uploader/additional-file-uploader.component'; @Injectable({providedIn: 'root'}) export class BookDialogHelperService { - private dialogService = inject(DialogService); + private dialogLauncherService = inject(DialogLauncherService); + + private openDialog(component: any, options: {}): DynamicDialogRef | null { + return this.dialogLauncherService.openDialog(component, options); + } - openShelfAssigner(bookIds: Set): DynamicDialogRef | null { - return this.dialogService.open(ShelfAssignerComponent, { - showHeader: false, - modal: true, - closable: true, - contentStyle: {overflow: 'hidden'}, - styleClass: 'dynamic-dialog-minimal', - baseZIndex: 10, + openBookDetailsDialog(bookId: number): DynamicDialogRef | null { + return this.openDialog(BookMetadataCenterComponent, { + header: 'Book Details', + styleClass: 'book-details-dialog dialog-maximal', data: { - isMultiBooks: true, - bookIds, + bookId: bookId, }, }); } - openShelfCreator(): DynamicDialogRef { - return this.dialogService.open(ShelfCreatorComponent, { + openShelfAssignerDialog(book: Book | null, bookIds: Set | null): DynamicDialogRef | null { + const data:any = {}; + if (book !== null) { + data.isMultiBooks = false; + data.book = book; + } else if (bookIds !== null) { + data.isMultiBooks = true; + data.bookIds = bookIds; + } else { + return null; + } + return this.openDialog(ShelfAssignerComponent, { + showHeader: false, + data: data, + styleClass: 'dynamic-dialog-minimal', + }); + } + + openShelfCreatorDialog(): DynamicDialogRef { + return this.openDialog(ShelfCreatorComponent, { showHeader: false, - modal: true, - draggable: false, - dismissableMask: true, - closable: true, - contentStyle: {overflow: 'auto'}, styleClass: 'dynamic-dialog-minimal', - baseZIndex: 10, - style: { - position: 'absolute', - top: '15%', - }, })!; } openLockUnlockMetadataDialog(bookIds: Set): DynamicDialogRef | null { const count = bookIds.size; - return this.dialogService.open(LockUnlockMetadataDialogComponent, { + return this.openDialog(LockUnlockMetadataDialogComponent, { header: `Lock or Unlock Metadata for ${count} Selected Book${count > 1 ? 's' : ''}`, - modal: true, - closable: true, data: { bookIds: Array.from(bookIds), }, @@ -59,70 +71,82 @@ export class BookDialogHelperService { } openMetadataRefreshDialog(bookIds: Set): DynamicDialogRef | null { - return this.dialogService.open(MultiBookMetadataFetchComponent, { + return this.openDialog(MultiBookMetadataFetchComponent, { header: 'Metadata Refresh Options', - modal: true, - closable: true, data: { bookIds: Array.from(bookIds), metadataRefreshType: MetadataRefreshType.BOOKS, }, + styleClass: 'dialog-maximal', }); } openBulkMetadataEditDialog(bookIds: Set): DynamicDialogRef | null { - return this.dialogService.open(BulkMetadataUpdateComponent, { + return this.openDialog(BulkMetadataUpdateComponent, { header: 'Bulk Edit Metadata', - modal: true, - closable: true, - style: { - width: '90vw', - maxWidth: '1200px', - position: 'absolute' - }, data: { - bookIds: Array.from(bookIds) + bookIds: Array.from(bookIds), }, + styleClass: 'dialog-maximal', }); } openMultibookMetadataEditorDialog(bookIds: Set): DynamicDialogRef | null { - return this.dialogService.open(MultiBookMetadataEditorComponent, { - header: 'Bulk Edit Metadata', - showHeader: false, - modal: true, - closable: true, - closeOnEscape: true, - dismissableMask: true, - style: { - width: '95vw', - overflow: 'none', - }, + return this.openDialog(MultiBookMetadataEditorComponent, { + header: 'Multi-Book Metadata Editor', data: { - bookIds: Array.from(bookIds) + bookIds: Array.from(bookIds), }, + styleClass: 'dialog-maximal', }); } - openFileMoverDialog(selectedBooks: Set) { - const count = selectedBooks.size; - return this.dialogService.open(FileMoverComponent, { + openFileMoverDialog(bookIds: Set): DynamicDialogRef | null { + const count = bookIds.size; + return this.openDialog(FileMoverComponent, { header: `Organize Book Files (${count} book${count !== 1 ? 's' : ''})`, - showHeader: true, - maximizable: true, - modal: true, - closable: true, - closeOnEscape: false, - dismissableMask: false, - style: { - width: '95vw', - maxWidth: '97.5vw', - height: '90vh', - maxHeight: '95vh' - }, data: { - bookIds: selectedBooks + bookIds: Array.from(bookIds), }, + styleClass: 'dialog-maximal', + }); + } + + openCustomSendDialog(bookId: number): DynamicDialogRef | null { + return this.openDialog(BookSenderComponent, { + header: 'Send Book to Email', + data: { + bookId: bookId, + } + }); + } + + openCoverSearchDialog(bookId: number): DynamicDialogRef | null { + return this.openDialog(CoverSearchComponent, { + header: "Search Cover", + data: { + bookId: bookId, + }, + styleClass: 'dialog-maximal', + }); + } + + openMetadataFetchOptionsDialog(bookId: number): DynamicDialogRef | null { + return this.openDialog(MetadataFetchOptionsComponent, { + header: 'Metadata Refresh Options', + data: { + bookIds: [bookId], + metadataRefreshType: MetadataRefreshType.BOOKS, + } + }); + } + + openAdditionalFileUploaderDialog(book: Book): DynamicDialogRef | null { + return this.openDialog(AdditionalFileUploaderComponent, { + header: 'Upload Additional File', + data: { + book: book, + } }); } } diff --git a/booklore-ui/src/app/features/book/components/book-browser/book-browser.component.ts b/booklore-ui/src/app/features/book/components/book-browser/book-browser.component.ts index b340a379..15af2c4f 100644 --- a/booklore-ui/src/app/features/book/components/book-browser/book-browser.component.ts +++ b/booklore-ui/src/app/features/book/components/book-browser/book-browser.component.ts @@ -639,7 +639,7 @@ export class BookBrowserComponent implements OnInit, AfterViewInit { } openShelfAssigner(): void { - this.dynamicDialogRef = this.dialogHelperService.openShelfAssigner(this.selectedBooks); + this.dynamicDialogRef = this.dialogHelperService.openShelfAssignerDialog(null, this.selectedBooks); } lockUnlockMetadata(): void { diff --git a/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.ts b/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.ts index 659879ef..12b0ec1b 100644 --- a/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.ts +++ b/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.ts @@ -4,8 +4,6 @@ import {AdditionalFile, Book, ReadStatus} from '../../../model/book.model'; import {Button} from 'primeng/button'; import {MenuModule} from 'primeng/menu'; import {ConfirmationService, MenuItem, MessageService} from 'primeng/api'; -import {DialogService} from 'primeng/dynamicdialog'; -import {ShelfAssignerComponent} from '../../shelf-assigner/shelf-assigner.component'; import {BookService} from '../../../service/book.service'; import {CheckboxChangeEvent, CheckboxModule} from 'primeng/checkbox'; import {FormsModule} from '@angular/forms'; @@ -16,16 +14,13 @@ import {UserService} from '../../../../settings/user-management/user.service'; import {filter, Subject} from 'rxjs'; import {EmailService} from '../../../../settings/email-v2/email.service'; import {TieredMenu} from 'primeng/tieredmenu'; -import {BookSenderComponent} from '../../book-sender/book-sender.component'; import {Router} from '@angular/router'; import {ProgressBar} from 'primeng/progressbar'; -import {BookMetadataCenterComponent} from '../../../../metadata/component/book-metadata-center/book-metadata-center.component'; import {take, takeUntil} from 'rxjs/operators'; import {readStatusLabels} from '../book-filter/book-filter.component'; import {ResetProgressTypes} from '../../../../../shared/constants/reset-progress-type'; import {ReadStatusHelper} from '../../../helpers/read-status.helper'; import {BookDialogHelperService} from '../BookDialogHelperService'; -import {MetadataFetchOptionsComponent} from '../../../../metadata/component/metadata-options-dialog/metadata-fetch-options/metadata-fetch-options.component'; import {TaskHelperService} from '../../../../settings/task-management/task-helper.service'; @Component({ @@ -59,7 +54,6 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy { private bookService = inject(BookService); private taskHelperService = inject(TaskHelperService); - private dialogService = inject(DialogService); private userService = inject(UserService); private emailService = inject(EmailService); private messageService = inject(MessageService); @@ -293,18 +287,7 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy { label: 'Custom Send', icon: 'pi pi-envelope', command: () => { - this.dialogService.open(BookSenderComponent, { - header: 'Send Book to Email', - modal: true, - closable: true, - style: { - position: 'absolute', - top: '15%', - }, - data: { - bookId: this.book.id, - } - }); + this.bookDialogHelperService.openCustomSendDialog(this.book.id); } } ] @@ -341,15 +324,7 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy { label: 'Custom Fetch', icon: 'pi pi-sync', command: () => { - this.dialogService.open(MetadataFetchOptionsComponent, { - header: 'Metadata Refresh Options', - modal: true, - closable: true, - data: { - bookIds: [this.book!.id], - metadataRefreshType: MetadataRefreshType.BOOKS, - }, - }); + this.bookDialogHelperService.openMetadataRefreshDialog(new Set([this.book!.id])) }, } ] @@ -457,19 +432,7 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy { } private openShelfDialog(): void { - this.dialogService.open(ShelfAssignerComponent, { - header: `Update Book's Shelves`, - showHeader: false, - modal: true, - dismissableMask: true, - closable: true, - contentStyle: {overflow: 'hidden'}, - styleClass: 'dynamic-dialog-minimal', - baseZIndex: 10, - data: { - book: this.book, - }, - }); + this.bookDialogHelperService.openShelfAssignerDialog(this.book, null); } openSeriesInfo(): void { @@ -488,21 +451,7 @@ export class BookCardComponent implements OnInit, OnChanges, OnDestroy { queryParams: {tab: 'view'} }); } else { - this.dialogService.open(BookMetadataCenterComponent, { - width: '90%', - height: '90%', - data: {bookId: book.id}, - modal: true, - dismissableMask: true, - showHeader: true, - closable: true, - closeOnEscape: true, - draggable: false, - maximizable: false, - resizable: false, - header: 'Book Details', - styleClass: 'book-details-dialog' - }); + this.bookDialogHelperService.openBookDetailsDialog(book.id); } } diff --git a/booklore-ui/src/app/features/book/components/book-browser/book-table/book-table.component.ts b/booklore-ui/src/app/features/book/components/book-browser/book-table/book-table.component.ts index 21b66898..b7b2dc7f 100644 --- a/booklore-ui/src/app/features/book/components/book-browser/book-table/book-table.component.ts +++ b/booklore-ui/src/app/features/book/components/book-browser/book-table/book-table.component.ts @@ -13,8 +13,6 @@ import {MessageService} from 'primeng/api'; import {Router, RouterLink} from '@angular/router'; import {filter, Subject} from 'rxjs'; import {UserService} from '../../../../settings/user-management/user.service'; -import {BookMetadataCenterComponent} from '../../../../metadata/component/book-metadata-center/book-metadata-center.component'; -import {DialogService} from 'primeng/dynamicdialog'; import {take, takeUntil} from 'rxjs/operators'; import {ReadStatusHelper} from '../../../helpers/read-status.helper'; diff --git a/booklore-ui/src/app/features/book/components/series-page/series-page.component.ts b/booklore-ui/src/app/features/book/components/series-page/series-page.component.ts index 543f839e..ccb24120 100644 --- a/booklore-ui/src/app/features/book/components/series-page/series-page.component.ts +++ b/booklore-ui/src/app/features/book/components/series-page/series-page.component.ts @@ -13,7 +13,7 @@ import { Tab, TabList, TabPanel, TabPanels, Tabs } from "primeng/tabs"; import { Tag } from "primeng/tag"; import { VirtualScrollerModule } from "@iharbeck/ngx-virtual-scroller"; import { ProgressSpinner } from "primeng/progressspinner"; -import { DialogService, DynamicDialogRef } from "primeng/dynamicdialog"; +import { DynamicDialogRef } from "primeng/dynamicdialog"; import { Router } from "@angular/router"; @Component({ diff --git a/booklore-ui/src/app/features/book/components/shelf-assigner/shelf-assigner.component.ts b/booklore-ui/src/app/features/book/components/shelf-assigner/shelf-assigner.component.ts index 68e7d97d..aa8b279f 100644 --- a/booklore-ui/src/app/features/book/components/shelf-assigner/shelf-assigner.component.ts +++ b/booklore-ui/src/app/features/book/components/shelf-assigner/shelf-assigner.component.ts @@ -91,7 +91,7 @@ export class ShelfAssignerComponent implements OnInit { } createShelfDialog(): void { - const dialogRef = this.bookDialogHelper.openShelfCreator(); + const dialogRef = this.bookDialogHelper.openShelfCreatorDialog(); dialogRef.onClose.subscribe((created: boolean) => { if (created) { diff --git a/booklore-ui/src/app/features/book/service/library-shelf-menu.service.ts b/booklore-ui/src/app/features/book/service/library-shelf-menu.service.ts index 33e69671..d860d916 100644 --- a/booklore-ui/src/app/features/book/service/library-shelf-menu.service.ts +++ b/booklore-ui/src/app/features/book/service/library-shelf-menu.service.ts @@ -5,19 +5,13 @@ import {LibraryService} from './library.service'; import {ShelfService} from './shelf.service'; import {Library} from '../model/library.model'; import {Shelf} from '../model/shelf.model'; -import {DialogService} from 'primeng/dynamicdialog'; import {MetadataRefreshType} from '../../metadata/model/request/metadata-refresh-type.enum'; -import {LibraryCreatorComponent} from '../../library-creator/library-creator.component'; -import {ShelfEditDialogComponent} from '../components/shelf-edit-dialog/shelf-edit-dialog.component'; import {MagicShelf, MagicShelfService} from '../../magic-shelf/service/magic-shelf.service'; -import {MetadataFetchOptionsComponent} from '../../metadata/component/metadata-options-dialog/metadata-fetch-options/metadata-fetch-options.component'; -import {MagicShelfComponent} from '../../magic-shelf/component/magic-shelf-component'; -import {TaskCreateRequest, TaskType} from '../../settings/task-management/task.service'; -import {MetadataRefreshRequest} from '../../metadata/model/request/metadata-refresh-request.model'; import {TaskHelperService} from '../../settings/task-management/task-helper.service'; import {UserService} from "../../settings/user-management/user.service"; import {LoadingService} from '../../../core/services/loading.service'; import {finalize} from 'rxjs'; +import {DialogLauncherService} from '../../../shared/services/dialog-launcher.service'; @Injectable({ providedIn: 'root', @@ -30,7 +24,7 @@ export class LibraryShelfMenuService { private shelfService = inject(ShelfService); private taskHelperService = inject(TaskHelperService); private router = inject(Router); - private dialogService = inject(DialogService); + private dialogLauncherService = inject(DialogLauncherService); private magicShelfService = inject(MagicShelfService); private userService = inject(UserService); private loadingService = inject(LoadingService); @@ -44,17 +38,7 @@ export class LibraryShelfMenuService { label: 'Edit Library', icon: 'pi pi-pen-to-square', command: () => { - this.dialogService.open(LibraryCreatorComponent, { - header: 'Edit Library', - modal: true, - closable: true, - showHeader: false, - styleClass: 'dynamic-dialog-minimal', - data: { - mode: 'edit', - libraryId: entity?.id - } - }); + this.dialogLauncherService.openLibraryEditDialog(entity?.id); } }, { @@ -93,15 +77,7 @@ export class LibraryShelfMenuService { label: 'Custom Fetch Metadata', icon: 'pi pi-sync', command: () => { - this.dialogService.open(MetadataFetchOptionsComponent, { - header: 'Metadata Refresh Options', - modal: true, - closable: true, - data: { - libraryId: entity?.id, - metadataRefreshType: MetadataRefreshType.LIBRARY - } - }) + this.dialogLauncherService.openLibraryMetadataFetchDialog(entity?.id); } }, { @@ -168,16 +144,7 @@ export class LibraryShelfMenuService { label: 'Edit Shelf', icon: 'pi pi-pen-to-square', command: () => { - this.dialogService.open(ShelfEditDialogComponent, { - header: 'Edit Shelf', - modal: true, - closable: true, - showHeader: false, - styleClass: 'dynamic-dialog-minimal', - data: { - shelfId: entity?.id - }, - }) + this.dialogLauncherService.openShelfEditDialog(entity?.id); } }, { @@ -230,17 +197,7 @@ export class LibraryShelfMenuService { icon: 'pi pi-pen-to-square', disabled: disableOptions, command: () => { - this.dialogService.open(MagicShelfComponent, { - header: 'Edit Magic Shelf', - modal: true, - closable: true, - showHeader: false, - styleClass: 'dynamic-dialog-minimal', - data: { - id: entity?.id, - editMode: true, - } - }) + this.dialogLauncherService.openMagicShelfEditDialog(entity?.id); } }, { diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.ts b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.ts index d1aa6c64..24945bba 100644 --- a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.ts +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.ts @@ -18,7 +18,6 @@ import {Observable, Subscription} from 'rxjs'; import {AppSettings} from '../../../../shared/model/app-settings.model'; import {AppSettingsService} from '../../../../shared/service/app-settings.service'; -import {DialogService} from 'primeng/dynamicdialog'; import {BookMetadata} from '../../../book/model/book.model'; import {UrlHelperService} from '../../../../shared/service/url-helper.service'; import {Checkbox} from 'primeng/checkbox'; @@ -26,7 +25,7 @@ import {NgClass, NgStyle} from '@angular/common'; import {Paginator} from 'primeng/paginator'; import {ActivatedRoute} from '@angular/router'; import {BookdropFileMetadataPickerComponent} from '../bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component'; -import {BookdropFinalizeResultDialogComponent} from '../bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component'; +import {DialogLauncherService} from '../../../../shared/services/dialog-launcher.service'; export interface BookdropFileUI { file: BookdropFile; @@ -64,7 +63,7 @@ export class BookdropFileReviewComponent implements OnInit { private readonly libraryService = inject(LibraryService); private readonly confirmationService = inject(ConfirmationService); private readonly destroyRef = inject(DestroyRef); - private readonly dialogService = inject(DialogService); + private readonly dialogLauncherService = inject(DialogLauncherService); private readonly appSettingsService = inject(AppSettingsService); private readonly messageService = inject(MessageService); private readonly urlHelper = inject(UrlHelperService); @@ -494,13 +493,7 @@ export class BookdropFileReviewComponent implements OnInit { detail: 'Import process finished. See details below.', }); - this.dialogService.open(BookdropFinalizeResultDialogComponent, { - header: 'Import Summary', - modal: true, - closable: true, - closeOnEscape: true, - data: {result: result}, - }); + this.dialogLauncherService.openBookdropFinalizeResultDialog(result); const finalizedIds = new Set(files.map(f => f.fileId)); Object.keys(this.fileUiCache).forEach(idStr => { diff --git a/booklore-ui/src/app/features/dashboard/components/dashboard-settings/dashboard-settings.component.scss b/booklore-ui/src/app/features/dashboard/components/dashboard-settings/dashboard-settings.component.scss index caad078d..4f7ead61 100644 --- a/booklore-ui/src/app/features/dashboard/components/dashboard-settings/dashboard-settings.component.scss +++ b/booklore-ui/src/app/features/dashboard/components/dashboard-settings/dashboard-settings.component.scss @@ -1,13 +1,11 @@ .dashboard-settings { - width: 1000px; - max-width: 1200px; + max-width: 600px; min-height: 300px; padding: 2rem 1rem 0 1rem; margin: 0 auto; @media (max-width: 768px) { width: 100%; - max-width: 100%; padding: 3rem 1rem 0 1rem; box-sizing: border-box; } diff --git a/booklore-ui/src/app/features/dashboard/components/main-dashboard/main-dashboard.component.ts b/booklore-ui/src/app/features/dashboard/components/main-dashboard/main-dashboard.component.ts index 73fb0eeb..74d29a4a 100644 --- a/booklore-ui/src/app/features/dashboard/components/main-dashboard/main-dashboard.component.ts +++ b/booklore-ui/src/app/features/dashboard/components/main-dashboard/main-dashboard.component.ts @@ -14,7 +14,6 @@ import {ProgressSpinner} from 'primeng/progressspinner'; import {TooltipModule} from 'primeng/tooltip'; import {DashboardConfigService} from '../../services/dashboard-config.service'; import {ScrollerConfig, ScrollerType} from '../../models/dashboard-config.model'; -import {DashboardSettingsComponent} from '../dashboard-settings/dashboard-settings.component'; import {MagicShelfService} from '../../../magic-shelf/service/magic-shelf.service'; import {BookRuleEvaluatorService} from '../../../magic-shelf/service/book-rule-evaluator.service'; import {GroupRule} from '../../../magic-shelf/component/magic-shelf-component'; @@ -39,7 +38,6 @@ const DEFAULT_MAX_ITEMS = 20; standalone: true }) export class MainDashboardComponent implements OnInit { - ref: DynamicDialogRef | undefined | null; private bookService = inject(BookService); private dialogLauncher = inject(DialogLauncherService); @@ -203,14 +201,10 @@ export class MainDashboardComponent implements OnInit { } openDashboardSettings(): void { - this.ref = this.dialogLauncher.open({ - component: DashboardSettingsComponent, - header: 'Configure Dashboard', - showHeader: false - }); + this.dialogLauncher.openDashboardSettingsDialog(); } createNewLibrary() { - this.dialogLauncher.openLibraryCreatorDialog(); + this.dialogLauncher.openLibraryCreateDialog(); } } diff --git a/booklore-ui/src/app/features/library-creator/library-creator.component.ts b/booklore-ui/src/app/features/library-creator/library-creator.component.ts index 4ee1e495..5c5ac6f0 100644 --- a/booklore-ui/src/app/features/library-creator/library-creator.component.ts +++ b/booklore-ui/src/app/features/library-creator/library-creator.component.ts @@ -1,6 +1,5 @@ import {Component, inject, OnInit} from '@angular/core'; -import {DialogService, DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; -import {DirectoryPickerComponent} from '../../shared/components/directory-picker/directory-picker.component'; +import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; import {MessageService} from 'primeng/api'; import {Router} from '@angular/router'; import {LibraryService} from '../book/service/library.service'; @@ -15,6 +14,7 @@ import {IconPickerService, IconSelection} from '../../shared/service/icon-picker import {Select} from 'primeng/select'; import {Button} from 'primeng/button'; import {IconDisplayComponent} from '../../shared/components/icon-display/icon-display.component'; +import {DialogLauncherService} from '../../shared/services/dialog-launcher.service'; @Component({ selector: 'app-library-creator', @@ -31,7 +31,6 @@ export class LibraryCreatorComponent implements OnInit { mode!: string; library!: Library | undefined; editModeLibraryName: string = ''; - directoryPickerDialogRef!: DynamicDialogRef | null; watch: boolean = false; scanMode: LibraryScanMode = 'FILE_AS_BOOK'; defaultBookFormat: BookFileType | undefined = undefined; @@ -48,7 +47,7 @@ export class LibraryCreatorComponent implements OnInit { {label: 'CBX/CBZ/CBR', value: 'CBX'} ]; - private dialogService = inject(DialogService); + private dialogLauncherService = inject(DialogLauncherService); private dynamicDialogRef = inject(DynamicDialogRef); private dynamicDialogConfig = inject(DynamicDialogConfig); private libraryService = inject(LibraryService); @@ -85,16 +84,8 @@ export class LibraryCreatorComponent implements OnInit { } openDirectoryPicker(): void { - this.directoryPickerDialogRef = this.dialogService.open(DirectoryPickerComponent, { - header: 'Select Media Directory', - showHeader: false, - modal: true, - closable: true, - styleClass: 'dynamic-dialog-minimal', - contentStyle: {overflow: 'hidden'}, - baseZIndex: 10 - }); - this.directoryPickerDialogRef?.onClose.subscribe((selectedFolders: string[] | null) => { + const ref = this.dialogLauncherService.openDirectoryPickerDialog(); + ref?.onClose.subscribe((selectedFolders: string[] | null) => { if (selectedFolders && selectedFolders.length > 0) { selectedFolders.forEach(folder => { if (!this.folders.includes(folder)) { diff --git a/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.scss b/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.scss index aa6e675c..c592618c 100644 --- a/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.scss +++ b/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.scss @@ -10,6 +10,10 @@ width: 250px; } +::ng-deep .cover-item p-image { + width: 150px; +} + ::ng-deep p-image img { margin: 0 auto; } diff --git a/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts b/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts index 6c806316..645df8af 100644 --- a/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts +++ b/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-editor/metadata-editor.component.ts @@ -14,7 +14,6 @@ import {BookService} from "../../../../book/service/book.service"; import {ProgressSpinner} from "primeng/progressspinner"; import {Tooltip} from "primeng/tooltip"; import {filter, take} from "rxjs/operators"; -import {DialogService} from "primeng/dynamicdialog"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; import {MetadataRefreshType} from "../../../model/request/metadata-refresh-type.enum"; import {AutoComplete} from "primeng/autocomplete"; @@ -22,8 +21,8 @@ import {DatePicker} from "primeng/datepicker"; import {Textarea} from "primeng/textarea"; import {Image} from "primeng/image"; import {LazyLoadImageModule} from "ng-lazyload-image"; -import {CoverSearchComponent} from '../../cover-search/cover-search.component'; import {TaskHelperService} from '../../../../settings/task-management/task-helper.service'; +import {BookDialogHelperService} from "../../../../book/components/book-browser/BookDialogHelperService"; @Component({ selector: "app-metadata-editor", @@ -61,7 +60,7 @@ export class MetadataEditorComponent implements OnInit { private bookService = inject(BookService); private taskHelperService = inject(TaskHelperService); protected urlHelper = inject(UrlHelperService); - private dialogService = inject(DialogService); + private bookDialogHelperService = inject(BookDialogHelperService); private destroyRef = inject(DestroyRef); metadataForm: FormGroup; @@ -684,21 +683,7 @@ export class MetadataEditorComponent implements OnInit { } openCoverSearch() { - const ref = this.dialogService.open(CoverSearchComponent, { - header: "Search Cover", - modal: true, - closable: true, - data: { - bookId: [this.currentBookId], - }, - style: { - width: "90vw", - height: "90vh", - maxWidth: "1200px", - position: "absolute", - }, - }); - + const ref = this.bookDialogHelperService.openCoverSearchDialog(this.currentBookId); ref?.onClose.subscribe((result) => { if (result) { this.metadataForm.get("thumbnailUrl")?.setValue(result); diff --git a/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts b/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts index c2b900d0..c56659e9 100644 --- a/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts +++ b/booklore-ui/src/app/features/metadata/component/book-metadata-center/metadata-viewer/metadata-viewer.component.ts @@ -10,10 +10,8 @@ import {UrlHelperService} from '../../../../../shared/service/url-helper.service import {UserService} from '../../../../settings/user-management/user.service'; import {SplitButton} from 'primeng/splitbutton'; import {ConfirmationService, MenuItem, MessageService} from 'primeng/api'; -import {BookSenderComponent} from '../../../../book/components/book-sender/book-sender.component'; -import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {DynamicDialogRef} from 'primeng/dynamicdialog'; import {EmailService} from '../../../../settings/email-v2/email.service'; -import {ShelfAssignerComponent} from '../../../../book/components/shelf-assigner/shelf-assigner.component'; import {Tooltip} from 'primeng/tooltip'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {Editor} from 'primeng/editor'; @@ -29,13 +27,10 @@ import {DatePicker} from 'primeng/datepicker'; import {Tab, TabList, TabPanel, TabPanels, Tabs} from 'primeng/tabs'; import {BookReviewsComponent} from '../../../../book/components/book-reviews/book-reviews.component'; import {ProgressSpinner} from 'primeng/progressspinner'; - import {TieredMenu} from 'primeng/tieredmenu'; -import {AdditionalFileUploaderComponent} from '../../../../book/components/additional-file-uploader/additional-file-uploader.component'; import {Image} from 'primeng/image'; import {BookDialogHelperService} from '../../../../book/components/book-browser/BookDialogHelperService'; import {TagColor, TagComponent} from '../../../../../shared/components/tag/tag.component'; -import {MetadataFetchOptionsComponent} from '../../metadata-options-dialog/metadata-fetch-options/metadata-fetch-options.component'; import {BookNotesComponent} from '../../../../book/components/book-notes/book-notes-component'; import {TaskHelperService} from '../../../../settings/task-management/task-helper.service'; import { @@ -57,7 +52,7 @@ export class MetadataViewerComponent implements OnInit, OnChanges { @ViewChild(Editor) quillEditor!: Editor; private originalRecommendedBooks: BookRecommendation[] = []; - private dialogService = inject(DialogService); + private bookDialogHelperService = inject(BookDialogHelperService) private emailService = inject(EmailService); private messageService = inject(MessageService); private bookService = inject(BookService); @@ -65,7 +60,6 @@ export class MetadataViewerComponent implements OnInit, OnChanges { protected urlHelper = inject(UrlHelperService); protected userService = inject(UserService); private confirmationService = inject(ConfirmationService); - private bookDialogHelperService = inject(BookDialogHelperService); private router = inject(Router); private destroyRef = inject(DestroyRef); @@ -105,13 +99,7 @@ export class MetadataViewerComponent implements OnInit, OnChanges { { label: 'Custom Send', command: () => { - this.dialogService.open(BookSenderComponent, { - header: 'Send Book to Email', - modal: true, - closable: true, - style: {position: 'absolute', top: '20%'}, - data: {bookId: metadata.bookId} - }); + this.bookDialogHelperService.openCustomSendDialog(metadata.bookId); } } ]) @@ -124,15 +112,7 @@ export class MetadataViewerComponent implements OnInit, OnChanges { label: 'Custom Fetch', icon: 'pi pi-sync', command: () => { - this.dialogService.open(MetadataFetchOptionsComponent, { - header: 'Metadata Refresh Options', - modal: true, - closable: true, - data: { - bookIds: [book.id], - metadataRefreshType: MetadataRefreshType.BOOKS, - }, - }); + this.bookDialogHelperService.openMetadataFetchOptionsDialog(book.id); } } ]) @@ -197,16 +177,7 @@ export class MetadataViewerComponent implements OnInit, OnChanges { label: 'Upload File', icon: 'pi pi-upload', command: () => { - this.dialogService.open(AdditionalFileUploaderComponent, { - header: 'Upload Additional File', - modal: true, - closable: true, - style: { - position: 'absolute', - top: '10%', - }, - data: {book} - }); + this.bookDialogHelperService.openAdditionalFileUploaderDialog(book); }, }, { @@ -425,16 +396,7 @@ export class MetadataViewerComponent implements OnInit, OnChanges { } assignShelf(bookId: number) { - this.dialogService.open(ShelfAssignerComponent, { - header: `Update Book's Shelves`, - showHeader: false, - dismissableMask: true, - modal: true, - closable: true, - contentStyle: {overflow: 'hidden'}, - baseZIndex: 10, - data: {book: this.bookService.getBookByIdFromState(bookId)} - }); + this.bookDialogHelperService.openShelfAssignerDialog(this.bookService.getBookByIdFromState(bookId), null); } updateReadStatus(status: ReadStatus): void { diff --git a/booklore-ui/src/app/features/metadata/component/metadata-review-dialog/metadata-review-dialog-component.html b/booklore-ui/src/app/features/metadata/component/metadata-review-dialog/metadata-review-dialog-component.html index f566117c..fe947ba5 100644 --- a/booklore-ui/src/app/features/metadata/component/metadata-review-dialog/metadata-review-dialog-component.html +++ b/booklore-ui/src/app/features/metadata/component/metadata-review-dialog/metadata-review-dialog-component.html @@ -1,10 +1,5 @@ @if (!loading) {
- -
-

Review Metadata Proposal

-
-
@if (currentProposal?.metadataJson; as proposed) { { if (result) { this.loadEmailProviders(); diff --git a/booklore-ui/src/app/features/settings/email-v2/email-v2-recipient/email-v2-recipient.component.ts b/booklore-ui/src/app/features/settings/email-v2/email-v2-recipient/email-v2-recipient.component.ts index c162a0e9..cd3f7ec5 100644 --- a/booklore-ui/src/app/features/settings/email-v2/email-v2-recipient/email-v2-recipient.component.ts +++ b/booklore-ui/src/app/features/settings/email-v2/email-v2-recipient/email-v2-recipient.component.ts @@ -1,15 +1,14 @@ import {Component, inject, OnInit} from '@angular/core'; import {Button} from 'primeng/button'; - import {MessageService, PrimeTemplate} from 'primeng/api'; import {RadioButton} from 'primeng/radiobutton'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {TableModule} from 'primeng/table'; import {Tooltip} from 'primeng/tooltip'; -import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {DynamicDialogRef} from 'primeng/dynamicdialog'; import {EmailV2RecipientService} from './email-v2-recipient.service'; import {EmailRecipient} from '../email-recipient.model'; -import {CreateEmailRecipientDialogComponent} from '../create-email-recipient-dialog/create-email-recipient-dialog.component'; +import {DialogLauncherService} from '../../../../shared/services/dialog-launcher.service'; @Component({ selector: 'app-email-v2-recipient', @@ -29,7 +28,7 @@ export class EmailV2RecipientComponent implements OnInit { recipientEmails: EmailRecipient[] = []; editingRecipientIds: number[] = []; ref: DynamicDialogRef | undefined | null; - private dialogService = inject(DialogService); + private dialogLauncherService = inject(DialogLauncherService); private emailRecipientService = inject(EmailV2RecipientService); private messageService = inject(MessageService); defaultRecipientId: any; @@ -111,13 +110,7 @@ export class EmailV2RecipientComponent implements OnInit { } openAddRecipientDialog() { - this.ref = this.dialogService.open(CreateEmailRecipientDialogComponent, { - header: 'Add New Recipient', - modal: true, - closable: true, - showHeader: false, - styleClass: 'dynamic-dialog-minimal', - }); + this.ref = this.dialogLauncherService.openEmailRecipientDialog(); this.ref?.onClose.subscribe((result) => { if (result) { this.loadRecipientEmails(); diff --git a/booklore-ui/src/app/features/settings/user-management/user-management.component.ts b/booklore-ui/src/app/features/settings/user-management/user-management.component.ts index 17d7e77a..835e8ff9 100644 --- a/booklore-ui/src/app/features/settings/user-management/user-management.component.ts +++ b/booklore-ui/src/app/features/settings/user-management/user-management.component.ts @@ -1,8 +1,7 @@ import {Component, inject, OnDestroy, OnInit} from '@angular/core'; import {FormsModule} from '@angular/forms'; import {Button} from 'primeng/button'; -import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog'; -import {CreateUserDialogComponent} from './create-user-dialog/create-user-dialog.component'; +import {DynamicDialogRef} from 'primeng/dynamicdialog'; import {TableModule} from 'primeng/table'; import {LowerCasePipe, TitleCasePipe} from '@angular/common'; import {User, UserService} from './user.service'; @@ -16,6 +15,7 @@ import {Password} from 'primeng/password'; import {filter, take, takeUntil} from 'rxjs/operators'; import {Subject} from 'rxjs'; import {Tooltip} from 'primeng/tooltip'; +import {DialogLauncherService} from '../../../shared/services/dialog-launcher.service'; @Component({ selector: 'app-user-management', @@ -36,7 +36,7 @@ import {Tooltip} from 'primeng/tooltip'; }) export class UserManagementComponent implements OnInit, OnDestroy { ref: DynamicDialogRef | undefined | null; - private dialogService = inject(DialogService); + private dialogLauncherService = inject(DialogLauncherService); private userService = inject(UserService); private libraryService = inject(LibraryService); private messageService = inject(MessageService); @@ -105,13 +105,7 @@ export class UserManagementComponent implements OnInit, OnDestroy { } openCreateUserDialog() { - this.ref = this.dialogService.open(CreateUserDialogComponent, { - header: 'Create New User', - showHeader: false, - modal: true, - closable: true, - styleClass: 'dynamic-dialog-minimal', - }); + this.ref = this.dialogLauncherService.openCreateUserDialog(); this.ref?.onClose.subscribe((result) => { if (result) { this.loadUsers(); diff --git a/booklore-ui/src/app/shared/components/metadata-progress-widget/metadata-progress-widget-component.ts b/booklore-ui/src/app/shared/components/metadata-progress-widget/metadata-progress-widget-component.ts index 381fc889..cfe99f23 100644 --- a/booklore-ui/src/app/shared/components/metadata-progress-widget/metadata-progress-widget-component.ts +++ b/booklore-ui/src/app/shared/components/metadata-progress-widget/metadata-progress-widget-component.ts @@ -6,15 +6,14 @@ import {ProgressBarModule} from 'primeng/progressbar'; import {ButtonModule} from 'primeng/button'; import {Divider} from 'primeng/divider'; import {Tooltip} from 'primeng/tooltip'; -import {DialogService} from 'primeng/dynamicdialog'; import {MessageService} from 'primeng/api'; import {MetadataBatchProgressNotification, MetadataBatchStatus, MetadataBatchStatusLabels} from '../../model/metadata-batch-progress.model'; import {MetadataProgressService} from '../../service/metadata-progress-service'; -import {MetadataReviewDialogComponent} from '../../../features/metadata/component/metadata-review-dialog/metadata-review-dialog-component'; import {MetadataTaskService} from '../../../features/book/service/metadata-task'; import {Tag} from 'primeng/tag'; import {TaskService} from '../../../features/settings/task-management/task.service'; +import {DialogLauncherService} from '../../services/dialog-launcher.service'; @Component({ selector: 'app-metadata-progress-widget', @@ -27,7 +26,7 @@ export class MetadataProgressWidgetComponent implements OnInit, OnDestroy { activeTasks: { [taskId: string]: MetadataBatchProgressNotification } = {}; private destroy$ = new Subject(); - private dialogService = inject(DialogService); + private dialogLauncherService = inject(DialogLauncherService); private metadataProgressService = inject(MetadataProgressService); private metadataTaskService = inject(MetadataTaskService); private taskService = inject(TaskService); @@ -102,14 +101,7 @@ export class MetadataProgressWidgetComponent implements OnInit, OnDestroy { } reviewTask(taskId: string): void { - this.dialogService.open(MetadataReviewDialogComponent, { - showHeader: false, - width: '90vw', - height: '90vh', - data: {taskId}, - closable: false, - modal: true - }); + this.dialogLauncherService.openMetadataReviewDialog(taskId); } cancelTask(taskId: string): void { diff --git a/booklore-ui/src/app/shared/layout/component/layout-menu/app.menu.component.ts b/booklore-ui/src/app/shared/layout/component/layout-menu/app.menu.component.ts index d42f23af..ba4d2993 100644 --- a/booklore-ui/src/app/shared/layout/component/layout-menu/app.menu.component.ts +++ b/booklore-ui/src/app/shared/layout/component/layout-menu/app.menu.component.ts @@ -9,10 +9,10 @@ import {ShelfService} from '../../../../features/book/service/shelf.service'; import {BookService} from '../../../../features/book/service/book.service'; import {LibraryShelfMenuService} from '../../../../features/book/service/library-shelf-menu.service'; import {AppVersion, VersionService} from '../../../service/version.service'; -import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog'; -import {VersionChangelogDialogComponent} from './version-changelog-dialog/version-changelog-dialog.component'; +import {DynamicDialogRef} from 'primeng/dynamicdialog'; import {UserService} from '../../../../features/settings/user-management/user.service'; import {MagicShelfService, MagicShelfState} from '../../../../features/magic-shelf/service/magic-shelf.service'; +import {DialogLauncherService} from '../../../services/dialog-launcher.service'; @Component({ selector: 'app-menu', @@ -34,7 +34,7 @@ export class AppMenuComponent implements OnInit { private bookService = inject(BookService); private versionService = inject(VersionService); private libraryShelfMenuService = inject(LibraryShelfMenuService); - private dialogService = inject(DialogService); + private dialogLauncherService = inject(DialogLauncherService); private userService = inject(UserService); private magicShelfService = inject(MagicShelfService); @@ -198,20 +198,7 @@ export class AppMenuComponent implements OnInit { } openChangelogDialog() { - const isMobile = window.innerWidth <= 768; - this.dynamicDialogRef = this.dialogService.open(VersionChangelogDialogComponent, { - header: 'Whatโ€™s New', - modal: true, - closable: true, - style: { - position: 'absolute', - top: '10%', - bottom: '10%', - width: isMobile ? '90vw' : '800px', - maxWidth: isMobile ? '90vw' : '800px', - minWidth: isMobile ? '90vw' : '800px', - }, - }); + this.dialogLauncherService.openVersionChangelogDialog(); } getVersionUrl(version: string | undefined): string { diff --git a/booklore-ui/src/app/shared/layout/component/layout-menu/app.menuitem.component.ts b/booklore-ui/src/app/shared/layout/component/layout-menu/app.menuitem.component.ts index 7ed172a0..235a0a94 100644 --- a/booklore-ui/src/app/shared/layout/component/layout-menu/app.menuitem.component.ts +++ b/booklore-ui/src/app/shared/layout/component/layout-menu/app.menuitem.component.ts @@ -155,13 +155,13 @@ export class AppMenuitemComponent implements OnInit, OnDestroy { openDialog(item: any) { if (item.type === 'library' && this.canManipulateLibrary) { - this.dialogLauncher.openLibraryCreatorDialog(); + this.dialogLauncher.openLibraryCreateDialog(); } if (item.type === 'magicShelf') { - this.dialogLauncher.openMagicShelfDialog(); + this.dialogLauncher.openMagicShelfCreateDialog(); } if (item.type === 'shelf') { - this.bookDialogHelperService.openShelfCreator(); + this.bookDialogHelperService.openShelfCreatorDialog(); } } diff --git a/booklore-ui/src/app/shared/layout/component/layout-topbar/app.topbar.component.ts b/booklore-ui/src/app/shared/layout/component/layout-topbar/app.topbar.component.ts index 2a40c78e..6e1fe88c 100644 --- a/booklore-ui/src/app/shared/layout/component/layout-topbar/app.topbar.component.ts +++ b/booklore-ui/src/app/shared/layout/component/layout-topbar/app.topbar.component.ts @@ -2,7 +2,7 @@ import {Component, ElementRef, OnDestroy, ViewChild} from '@angular/core'; import {MenuItem} from 'primeng/api'; import {LayoutService} from '../layout-main/service/app.layout.service'; import {Router, RouterLink} from '@angular/router'; -import {DialogService as PrimeDialogService, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {DynamicDialogRef} from 'primeng/dynamicdialog'; import {TooltipModule} from 'primeng/tooltip'; import {FormsModule} from '@angular/forms'; import {InputTextModule} from 'primeng/inputtext'; @@ -72,7 +72,6 @@ export class AppTopBarComponent implements OnDestroy { constructor( public layoutService: LayoutService, - public dialogService: PrimeDialogService, private notificationService: NotificationEventService, private router: Router, private authService: AuthService, @@ -120,7 +119,7 @@ export class AppTopBarComponent implements OnDestroy { } openLibraryCreatorDialog(): void { - this.dialogLauncher.openLibraryCreatorDialog(); + this.dialogLauncher.openLibraryCreateDialog(); } openFileUploadDialog(): void { diff --git a/booklore-ui/src/app/shared/service/icon-picker.service.ts b/booklore-ui/src/app/shared/service/icon-picker.service.ts index e837cc65..056db3ba 100644 --- a/booklore-ui/src/app/shared/service/icon-picker.service.ts +++ b/booklore-ui/src/app/shared/service/icon-picker.service.ts @@ -1,7 +1,6 @@ import {inject, Injectable} from '@angular/core'; -import {DialogService, DynamicDialogRef} from 'primeng/dynamicdialog'; -import {IconPickerComponent} from '../components/icon-picker/icon-picker-component'; import {Observable} from 'rxjs'; +import {DialogLauncherService} from '../services/dialog-launcher.service'; export interface IconSelection { type: 'PRIME_NG' | 'CUSTOM_SVG'; @@ -10,23 +9,10 @@ export interface IconSelection { @Injectable({providedIn: 'root'}) export class IconPickerService { - private dialog = inject(DialogService); + private dialogLauncherService = inject(DialogLauncherService); open(): Observable { - const isMobile = window.innerWidth <= 768; - const ref: DynamicDialogRef | null = this.dialog.open(IconPickerComponent, { - header: 'Choose an Icon', - modal: true, - closable: true, - style: { - position: 'absolute', - top: '10%', - bottom: '10%', - width: isMobile ? '90vw' : '800px', - maxWidth: isMobile ? '90vw' : '800px', - minWidth: isMobile ? '90vw' : '800px', - } - }); + const ref = this.dialogLauncherService.openIconPickerDialog(); return ref!.onClose as Observable; } } diff --git a/booklore-ui/src/app/shared/services/dialog-launcher.service.ts b/booklore-ui/src/app/shared/services/dialog-launcher.service.ts index acd71839..68c3aa8b 100644 --- a/booklore-ui/src/app/shared/services/dialog-launcher.service.ts +++ b/booklore-ui/src/app/shared/services/dialog-launcher.service.ts @@ -5,6 +5,19 @@ import {LibraryCreatorComponent} from '../../features/library-creator/library-cr import {BookUploaderComponent} from '../components/book-uploader/book-uploader.component'; import {UserProfileDialogComponent} from '../../features/settings/user-profile-dialog/user-profile-dialog.component'; import {MagicShelfComponent} from '../../features/magic-shelf/component/magic-shelf-component'; +import {DashboardSettingsComponent} from '../../features/dashboard/components/dashboard-settings/dashboard-settings.component'; +import {VersionChangelogDialogComponent} from '../layout/component/layout-menu/version-changelog-dialog/version-changelog-dialog.component'; +import {CreateUserDialogComponent} from '../../features/settings/user-management/create-user-dialog/create-user-dialog.component'; +import {CreateEmailRecipientDialogComponent} from '../../features/settings/email-v2/create-email-recipient-dialog/create-email-recipient-dialog.component'; +import {CreateEmailProviderDialogComponent} from '../../features/settings/email-v2/create-email-provider-dialog/create-email-provider-dialog.component'; +import {DirectoryPickerComponent} from '../components/directory-picker/directory-picker.component'; +import {BookdropFinalizeResultDialogComponent} from '../../features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component'; +import {BookdropFinalizeResult} from '../../features/bookdrop/service/bookdrop.service'; +import {MetadataReviewDialogComponent} from '../../features/metadata/component/metadata-review-dialog/metadata-review-dialog-component'; +import {MetadataRefreshType} from '../../features/metadata/model/request/metadata-refresh-type.enum'; +import {MetadataFetchOptionsComponent} from '../../features/metadata/component/metadata-options-dialog/metadata-fetch-options/metadata-fetch-options.component'; +import {ShelfEditDialogComponent} from '../../features/book/components/shelf-edit-dialog/shelf-edit-dialog.component'; +import {IconPickerComponent} from '../components/icon-picker/icon-picker-component'; @Injectable({ providedIn: 'root', @@ -13,73 +26,164 @@ export class DialogLauncherService { dialogService = inject(DialogService); - open(options: { component: any; header: string; top?: string; width?: string; showHeader?: boolean; styleClass?: string }): DynamicDialogRef | null { - const isMobile = window.innerWidth <= 768; - const {component, header, top, width, showHeader = true, styleClass} = options; + private defaultDialogOptions = { + baseZIndex: 10, + closable: true, + dismissableMask: true, + draggable: false, + modal: true, + resizable: false, + showHeader: true, + } + + openDialog(component: any, options: {}): DynamicDialogRef | null { return this.dialogService.open(component, { - header, - showHeader, - modal: true, - closable: true, - styleClass: styleClass, - style: { - position: 'absolute', - ...(top ? {top} : {}), - ...(isMobile - ? { - width: '90vw', - maxWidth: '90vw', - minWidth: '90vw', - } - : width - ? {width} - : {}), + ...this.defaultDialogOptions, + ...options, + }); + } + + openDashboardSettingsDialog(): DynamicDialogRef | null { + return this.openDialog(DashboardSettingsComponent, { + header: 'Configure Dashboard', + }); + } + + openGithubSupportDialog(): DynamicDialogRef | null { + return this.openDialog(GithubSupportDialog, { + header: 'Support Booklore', + }); + } + + openLibraryCreateDialog(): DynamicDialogRef | null { + return this.openDialog(LibraryCreatorComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + }); + } + + openDirectoryPickerDialog(): DynamicDialogRef | null { + return this.openDialog(DirectoryPickerComponent, { + header: 'Select Media Directory', + styleClass: 'dynamic-dialog-minimal', + }); + } + + openLibraryEditDialog(libraryId: number): DynamicDialogRef | null { + return this.openDialog(LibraryCreatorComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + data: { + mode: 'edit', + libraryId: libraryId + } + }); + } + + openLibraryMetadataFetchDialog(libraryId: number): DynamicDialogRef | null { + return this.openDialog(MetadataFetchOptionsComponent, { + header: 'Metadata Refresh Options', + data: { + libraryId: libraryId, + metadataRefreshType: MetadataRefreshType.LIBRARY, + } + }); + } + + openShelfEditDialog(shelfId: number): DynamicDialogRef | null { + return this.openDialog(ShelfEditDialogComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + data: { + shelfId: shelfId + }, + }) + } + + openFileUploadDialog(): DynamicDialogRef | null { + return this.openDialog(BookUploaderComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + }); + } + + openCreateUserDialog(): DynamicDialogRef | null { + return this.openDialog(CreateUserDialogComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + }); + } + + openUserProfileDialog(): DynamicDialogRef | null { + return this.openDialog(UserProfileDialogComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + }); + } + + openMagicShelfCreateDialog(): DynamicDialogRef | null { + return this.openDialog(MagicShelfComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + }); + } + + openMagicShelfEditDialog(shelfId: number): DynamicDialogRef | null { + return this.openDialog(MagicShelfComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + data: { + id: shelfId, + editMode: true, + } + }) + } + + openVersionChangelogDialog(): DynamicDialogRef | null { + return this.openDialog(VersionChangelogDialogComponent, { + header: "What's New", + styleClass: 'dialog-maximal', + }); + } + + openEmailRecipientDialog(): DynamicDialogRef | null { + return this.openDialog(CreateEmailRecipientDialogComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + }); + } + + openEmailProviderDialog(): DynamicDialogRef | null { + return this.openDialog(CreateEmailProviderDialogComponent, { + showHeader: false, + styleClass: 'dynamic-dialog-minimal', + }); + } + + openBookdropFinalizeResultDialog(result: BookdropFinalizeResult): DynamicDialogRef | null { + return this.openDialog(BookdropFinalizeResultDialogComponent, { + header: 'Import Summary', + data: { + result: result, }, }); } - openGithubSupportDialog(): void { - this.open({ - component: GithubSupportDialog, - header: 'Support Booklore', - showHeader: true, - top: '15%' + openMetadataReviewDialog(taskId: string): DynamicDialogRef | null { + return this.openDialog(MetadataReviewDialogComponent, { + header: 'Review Metadata Proposal', + data: { + taskId, + }, + styleClass: 'dialog-maximal', }); } - openLibraryCreatorDialog(): void { - this.open({ - component: LibraryCreatorComponent, - header: 'Create New Library', - styleClass: 'dynamic-dialog-minimal', - showHeader: false + openIconPickerDialog(): DynamicDialogRef | null { + return this.openDialog(IconPickerComponent, { + header: 'Choose an Icon', + styleClass: 'dialog-maximal', }); } - openFileUploadDialog(): void { - this.open({ - component: BookUploaderComponent, - header: 'Book Uploader', - showHeader: false, - styleClass: 'dynamic-dialog-minimal' - }); - } - - openUserProfileDialog(): void { - this.open({ - component: UserProfileDialogComponent, - header: 'User Profile Information', - styleClass: 'dynamic-dialog-minimal', - showHeader: false - }); - } - - openMagicShelfDialog(): void { - this.open({ - component: MagicShelfComponent, - header: 'Magic Shelf Creator', - styleClass: 'dynamic-dialog-minimal', - showHeader: false - }); - } -} +} \ No newline at end of file diff --git a/booklore-ui/src/assets/layout/styles/global.scss b/booklore-ui/src/assets/layout/styles/global.scss index 4274a346..7c42dddf 100644 --- a/booklore-ui/src/assets/layout/styles/global.scss +++ b/booklore-ui/src/assets/layout/styles/global.scss @@ -4,6 +4,14 @@ display: none; } +.p-dialog { + @media (max-width: 768px) { + width: 95vw !important; + max-width: 95vw !important; + max-height: 95vh !important; + } +} + .dynamic-dialog-minimal.p-dialog { border: none; @@ -12,6 +20,13 @@ } } +.dialog-maximal.p-dialog { + width: 95vw; + max-width: 1200px; + height: 95vh; + max-height: 95vh; +} + .gradient-divider { min-height: 1px; height: 1px; From 708e851e0b4b809d5d6dc1595b8c3de84b4848e4 Mon Sep 17 00:00:00 2001 From: Dmitry Manannikov Date: Sat, 13 Dec 2025 20:16:56 -0800 Subject: [PATCH 08/46] Add support for fb2 books (#1757) --- .../model/enums/BookFileExtension.java | 3 +- .../booklore/model/enums/BookFileType.java | 2 +- .../service/fileprocessor/Fb2Processor.java | 143 +++ .../extractor/Fb2MetadataExtractor.java | 362 +++++++ .../extractor/MetadataExtractorFactory.java | 4 + .../extractor/Fb2MetadataExtractorTest.java | 885 ++++++++++++++++++ .../book-card/book-card.component.html | 2 +- .../book-card/book-card.component.ts | 5 + .../src/app/features/book/model/book.model.ts | 2 +- .../app/features/book/model/library.model.ts | 2 +- .../metadata-viewer.component.html | 2 +- .../book-uploader.component.html | 4 +- 12 files changed, 1408 insertions(+), 8 deletions(-) create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/service/fileprocessor/Fb2Processor.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/extractor/Fb2MetadataExtractor.java create mode 100644 booklore-api/src/test/java/com/adityachandel/booklore/service/metadata/extractor/Fb2MetadataExtractorTest.java diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/BookFileExtension.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/BookFileExtension.java index 67d5f9a5..5186d61d 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/BookFileExtension.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/BookFileExtension.java @@ -13,7 +13,8 @@ public enum BookFileExtension { EPUB("epub", BookFileType.EPUB), CBZ("cbz", BookFileType.CBX), CBR("cbr", BookFileType.CBX), - CB7("cb7", BookFileType.CBX); + CB7("cb7", BookFileType.CBX), + FB2("fb2", BookFileType.FB2); private final String extension; private final BookFileType type; diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/BookFileType.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/BookFileType.java index b16361b3..21a9c9a2 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/BookFileType.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/BookFileType.java @@ -1,5 +1,5 @@ package com.adityachandel.booklore.model.enums; public enum BookFileType { - PDF, EPUB, CBX + PDF, EPUB, CBX, FB2 } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/fileprocessor/Fb2Processor.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/fileprocessor/Fb2Processor.java new file mode 100644 index 00000000..467ff3a2 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/fileprocessor/Fb2Processor.java @@ -0,0 +1,143 @@ +package com.adityachandel.booklore.service.fileprocessor; + +import com.adityachandel.booklore.mapper.BookMapper; +import com.adityachandel.booklore.model.dto.BookMetadata; +import com.adityachandel.booklore.model.dto.settings.LibraryFile; +import com.adityachandel.booklore.model.entity.BookEntity; +import com.adityachandel.booklore.model.entity.BookMetadataEntity; +import com.adityachandel.booklore.model.enums.BookFileType; +import com.adityachandel.booklore.repository.BookAdditionalFileRepository; +import com.adityachandel.booklore.repository.BookMetadataRepository; +import com.adityachandel.booklore.repository.BookRepository; +import com.adityachandel.booklore.service.book.BookCreatorService; +import com.adityachandel.booklore.service.metadata.MetadataMatchService; +import com.adityachandel.booklore.service.metadata.extractor.Fb2MetadataExtractor; +import com.adityachandel.booklore.util.FileService; +import com.adityachandel.booklore.util.FileUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.time.Instant; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.adityachandel.booklore.util.FileService.truncate; + +@Slf4j +@Service +public class Fb2Processor extends AbstractFileProcessor implements BookFileProcessor { + + private final Fb2MetadataExtractor fb2MetadataExtractor; + private final BookMetadataRepository bookMetadataRepository; + + public Fb2Processor(BookRepository bookRepository, + BookAdditionalFileRepository bookAdditionalFileRepository, + BookCreatorService bookCreatorService, + BookMapper bookMapper, + FileService fileService, + BookMetadataRepository bookMetadataRepository, + MetadataMatchService metadataMatchService, + Fb2MetadataExtractor fb2MetadataExtractor) { + super(bookRepository, bookAdditionalFileRepository, bookCreatorService, bookMapper, fileService, metadataMatchService); + this.fb2MetadataExtractor = fb2MetadataExtractor; + this.bookMetadataRepository = bookMetadataRepository; + } + + @Override + public BookEntity processNewFile(LibraryFile libraryFile) { + BookEntity bookEntity = bookCreatorService.createShellBook(libraryFile, BookFileType.FB2); + setBookMetadata(bookEntity); + if (generateCover(bookEntity)) { + FileService.setBookCoverPath(bookEntity.getMetadata()); + } + return bookEntity; + } + + @Override + public boolean generateCover(BookEntity bookEntity) { + try { + File fb2File = new File(FileUtils.getBookFullPath(bookEntity)); + byte[] coverData = fb2MetadataExtractor.extractCover(fb2File); + + if (coverData == null || coverData.length == 0) { + log.warn("No cover image found in FB2 '{}'", bookEntity.getFileName()); + return false; + } + + boolean saved = saveCoverImage(coverData, bookEntity.getId()); + bookEntity.getMetadata().setCoverUpdatedOn(Instant.now()); + bookMetadataRepository.save(bookEntity.getMetadata()); + return saved; + + } catch (Exception e) { + log.error("Error generating cover for FB2 '{}': {}", bookEntity.getFileName(), e.getMessage(), e); + return false; + } + } + + @Override + public List getSupportedTypes() { + return List.of(BookFileType.FB2); + } + + private void setBookMetadata(BookEntity bookEntity) { + File bookFile = new File(bookEntity.getFullFilePath().toUri()); + BookMetadata fb2Metadata = fb2MetadataExtractor.extractMetadata(bookFile); + if (fb2Metadata == null) return; + + BookMetadataEntity metadata = bookEntity.getMetadata(); + + metadata.setTitle(truncate(fb2Metadata.getTitle(), 1000)); + metadata.setSubtitle(truncate(fb2Metadata.getSubtitle(), 1000)); + metadata.setDescription(truncate(fb2Metadata.getDescription(), 2000)); + metadata.setPublisher(truncate(fb2Metadata.getPublisher(), 1000)); + metadata.setPublishedDate(fb2Metadata.getPublishedDate()); + metadata.setSeriesName(truncate(fb2Metadata.getSeriesName(), 1000)); + metadata.setSeriesNumber(fb2Metadata.getSeriesNumber()); + metadata.setSeriesTotal(fb2Metadata.getSeriesTotal()); + metadata.setIsbn13(truncate(fb2Metadata.getIsbn13(), 64)); + metadata.setIsbn10(truncate(fb2Metadata.getIsbn10(), 64)); + metadata.setPageCount(fb2Metadata.getPageCount()); + + String lang = fb2Metadata.getLanguage(); + metadata.setLanguage(truncate((lang == null || "UND".equalsIgnoreCase(lang)) ? "en" : lang, 1000)); + + metadata.setAsin(truncate(fb2Metadata.getAsin(), 20)); + metadata.setPersonalRating(fb2Metadata.getPersonalRating()); + metadata.setAmazonRating(fb2Metadata.getAmazonRating()); + metadata.setAmazonReviewCount(fb2Metadata.getAmazonReviewCount()); + metadata.setGoodreadsId(truncate(fb2Metadata.getGoodreadsId(), 100)); + metadata.setGoodreadsRating(fb2Metadata.getGoodreadsRating()); + metadata.setGoodreadsReviewCount(fb2Metadata.getGoodreadsReviewCount()); + metadata.setHardcoverId(truncate(fb2Metadata.getHardcoverId(), 100)); + metadata.setHardcoverRating(fb2Metadata.getHardcoverRating()); + metadata.setHardcoverReviewCount(fb2Metadata.getHardcoverReviewCount()); + metadata.setGoogleId(truncate(fb2Metadata.getGoogleId(), 100)); + metadata.setComicvineId(truncate(fb2Metadata.getComicvineId(), 100)); + + bookCreatorService.addAuthorsToBook(fb2Metadata.getAuthors(), bookEntity); + + if (fb2Metadata.getCategories() != null) { + Set validSubjects = fb2Metadata.getCategories().stream() + .filter(s -> s != null && !s.isBlank() && s.length() <= 100 && !s.contains("\n") && !s.contains("\r") && !s.contains(" ")) + .collect(Collectors.toSet()); + bookCreatorService.addCategoriesToBook(validSubjects, bookEntity); + } + } + + private boolean saveCoverImage(byte[] coverData, long bookId) throws Exception { + BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(coverData)); + try { + return fileService.saveCoverImages(originalImage, bookId); + } finally { + if (originalImage != null) { + originalImage.flush(); // Release resources after processing + } + } + } +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/extractor/Fb2MetadataExtractor.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/extractor/Fb2MetadataExtractor.java new file mode 100644 index 00000000..b7e957fd --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/extractor/Fb2MetadataExtractor.java @@ -0,0 +1,362 @@ +package com.adityachandel.booklore.service.metadata.extractor; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.time.LocalDate; +import java.util.Base64; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; + +@Slf4j +@Component +public class Fb2MetadataExtractor implements FileMetadataExtractor { + + private static final String FB2_NAMESPACE = "http://www.gribuser.ru/xml/fictionbook/2.0"; + private static final Pattern YEAR_PATTERN = Pattern.compile("\\d{4}"); + private static final Pattern ISBN_PATTERN = Pattern.compile("\\d{9}[\\dXx]"); + + @Override + public byte[] extractCover(File file) { + try (InputStream inputStream = getInputStream(file)) { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + DocumentBuilder builder = dbf.newDocumentBuilder(); + Document doc = builder.parse(inputStream); + + // Look for cover image in binary elements + NodeList binaries = doc.getElementsByTagNameNS(FB2_NAMESPACE, "binary"); + for (int i = 0; i < binaries.getLength(); i++) { + Element binary = (Element) binaries.item(i); + String id = binary.getAttribute("id"); + + if (id != null && id.toLowerCase().contains("cover")) { + String contentType = binary.getAttribute("content-type"); + if (contentType != null && contentType.startsWith("image/")) { + String base64Data = binary.getTextContent().trim(); + return Base64.getDecoder().decode(base64Data); + } + } + } + + // If no cover found by name, try to find the first referenced image in title-info + Element titleInfo = getFirstElementByTagNameNS(doc, FB2_NAMESPACE, "title-info"); + if (titleInfo != null) { + NodeList coverPages = titleInfo.getElementsByTagNameNS(FB2_NAMESPACE, "coverpage"); + if (coverPages.getLength() > 0) { + Element coverPage = (Element) coverPages.item(0); + NodeList images = coverPage.getElementsByTagNameNS(FB2_NAMESPACE, "image"); + if (images.getLength() > 0) { + Element image = (Element) images.item(0); + String href = image.getAttributeNS("http://www.w3.org/1999/xlink", "href"); + if (href != null && href.startsWith("#")) { + String imageId = href.substring(1); + // Find the binary with this ID + for (int i = 0; i < binaries.getLength(); i++) { + Element binary = (Element) binaries.item(i); + if (imageId.equals(binary.getAttribute("id"))) { + String base64Data = binary.getTextContent().trim(); + return Base64.getDecoder().decode(base64Data); + } + } + } + } + } + } + + return null; + } catch (Exception e) { + log.warn("Failed to extract cover from FB2: {}", file.getName(), e); + return null; + } + } + + @Override + public BookMetadata extractMetadata(File file) { + try (InputStream inputStream = getInputStream(file)) { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + DocumentBuilder builder = dbf.newDocumentBuilder(); + Document doc = builder.parse(inputStream); + + BookMetadata.BookMetadataBuilder metadataBuilder = BookMetadata.builder(); + Set authors = new HashSet<>(); + Set categories = new HashSet<>(); + + // Extract title-info (main metadata section) + Element titleInfo = getFirstElementByTagNameNS(doc, FB2_NAMESPACE, "title-info"); + if (titleInfo != null) { + extractTitleInfo(titleInfo, metadataBuilder, authors, categories); + } + + // Extract publish-info (publisher, year, ISBN) + Element publishInfo = getFirstElementByTagNameNS(doc, FB2_NAMESPACE, "publish-info"); + if (publishInfo != null) { + extractPublishInfo(publishInfo, metadataBuilder); + } + + // Extract document-info (optional metadata) + Element documentInfo = getFirstElementByTagNameNS(doc, FB2_NAMESPACE, "document-info"); + if (documentInfo != null) { + extractDocumentInfo(documentInfo, metadataBuilder); + } + + metadataBuilder.authors(authors); + metadataBuilder.categories(categories); + + return metadataBuilder.build(); + } catch (Exception e) { + log.warn("Failed to extract metadata from FB2: {}", file.getName(), e); + return null; + } + } + + private void extractTitleInfo(Element titleInfo, BookMetadata.BookMetadataBuilder builder, + Set authors, Set categories) { + // Extract genres (categories) + NodeList genres = titleInfo.getElementsByTagNameNS(FB2_NAMESPACE, "genre"); + for (int i = 0; i < genres.getLength(); i++) { + String genre = genres.item(i).getTextContent().trim(); + if (StringUtils.isNotBlank(genre)) { + categories.add(genre); + } + } + + // Extract authors + NodeList authorNodes = titleInfo.getElementsByTagNameNS(FB2_NAMESPACE, "author"); + for (int i = 0; i < authorNodes.getLength(); i++) { + Element author = (Element) authorNodes.item(i); + String authorName = extractPersonName(author); + if (StringUtils.isNotBlank(authorName)) { + authors.add(authorName); + } + } + + // Extract book title + Element bookTitle = getFirstElementByTagNameNS(titleInfo, FB2_NAMESPACE, "book-title"); + if (bookTitle != null) { + builder.title(bookTitle.getTextContent().trim()); + } + + // Extract annotation (description) + Element annotation = getFirstElementByTagNameNS(titleInfo, FB2_NAMESPACE, "annotation"); + if (annotation != null) { + String description = extractTextFromElement(annotation); + if (StringUtils.isNotBlank(description)) { + builder.description(description); + } + } + + // Extract keywords (additional categories/tags) + Element keywords = getFirstElementByTagNameNS(titleInfo, FB2_NAMESPACE, "keywords"); + if (keywords != null) { + String keywordsText = keywords.getTextContent().trim(); + if (StringUtils.isNotBlank(keywordsText)) { + for (String keyword : keywordsText.split("[,;]")) { + String trimmed = keyword.trim(); + if (StringUtils.isNotBlank(trimmed)) { + categories.add(trimmed); + } + } + } + } + + // Extract date + Element date = getFirstElementByTagNameNS(titleInfo, FB2_NAMESPACE, "date"); + if (date != null) { + String dateValue = date.getAttribute("value"); + if (StringUtils.isBlank(dateValue)) { + dateValue = date.getTextContent().trim(); + } + LocalDate publishedDate = parseDate(dateValue); + if (publishedDate != null) { + builder.publishedDate(publishedDate); + } + } + + // Extract language + Element lang = getFirstElementByTagNameNS(titleInfo, FB2_NAMESPACE, "lang"); + if (lang != null) { + builder.language(lang.getTextContent().trim()); + } + + // Extract sequence (series information) + Element sequence = getFirstElementByTagNameNS(titleInfo, FB2_NAMESPACE, "sequence"); + if (sequence != null) { + String seriesName = sequence.getAttribute("name"); + if (StringUtils.isNotBlank(seriesName)) { + builder.seriesName(seriesName.trim()); + } + String seriesNumber = sequence.getAttribute("number"); + if (StringUtils.isNotBlank(seriesNumber)) { + try { + builder.seriesNumber(Float.parseFloat(seriesNumber)); + } catch (NumberFormatException e) { + log.debug("Failed to parse series number: {}", seriesNumber); + } + } + } + } + + private void extractPublishInfo(Element publishInfo, BookMetadata.BookMetadataBuilder builder) { + // Extract publisher + Element publisher = getFirstElementByTagNameNS(publishInfo, FB2_NAMESPACE, "publisher"); + if (publisher != null) { + builder.publisher(publisher.getTextContent().trim()); + } + + // Extract publication year + Element year = getFirstElementByTagNameNS(publishInfo, FB2_NAMESPACE, "year"); + if (year != null) { + String yearText = year.getTextContent().trim(); + Matcher matcher = YEAR_PATTERN.matcher(yearText); + if (matcher.find()) { + try { + int yearValue = Integer.parseInt(matcher.group()); + builder.publishedDate(LocalDate.of(yearValue, 1, 1)); + } catch (NumberFormatException e) { + log.debug("Failed to parse year: {}", yearText); + } + } + } + + // Extract ISBN + Element isbn = getFirstElementByTagNameNS(publishInfo, FB2_NAMESPACE, "isbn"); + if (isbn != null) { + String isbnText = isbn.getTextContent().trim().replaceAll("[^0-9Xx]", ""); + if (isbnText.length() == 13) { + builder.isbn13(isbnText); + } else if (isbnText.length() == 10) { + builder.isbn10(isbnText); + } else if (ISBN_PATTERN.matcher(isbnText).find()) { + // Extract the first valid ISBN pattern found + Matcher matcher = ISBN_PATTERN.matcher(isbnText); + if (matcher.find()) { + builder.isbn10(matcher.group()); + } + } + } + } + + private void extractDocumentInfo(Element documentInfo, BookMetadata.BookMetadataBuilder builder) { + // Extract document ID (can be used as an identifier) + Element id = getFirstElementByTagNameNS(documentInfo, FB2_NAMESPACE, "id"); + if (id != null) { + // Could potentially map this to a custom identifier field if needed + log.debug("FB2 document ID: {}", id.getTextContent().trim()); + } + } + + private String extractPersonName(Element personElement) { + Element firstName = getFirstElementByTagNameNS(personElement, FB2_NAMESPACE, "first-name"); + Element middleName = getFirstElementByTagNameNS(personElement, FB2_NAMESPACE, "middle-name"); + Element lastName = getFirstElementByTagNameNS(personElement, FB2_NAMESPACE, "last-name"); + Element nickname = getFirstElementByTagNameNS(personElement, FB2_NAMESPACE, "nickname"); + + StringBuilder name = new StringBuilder(); + + if (firstName != null) { + name.append(firstName.getTextContent().trim()); + } + if (middleName != null) { + if (name.length() > 0) name.append(" "); + name.append(middleName.getTextContent().trim()); + } + if (lastName != null) { + if (name.length() > 0) name.append(" "); + name.append(lastName.getTextContent().trim()); + } + + // If no name parts found, try nickname + if (name.length() == 0 && nickname != null) { + name.append(nickname.getTextContent().trim()); + } + + return name.toString(); + } + + private String extractTextFromElement(Element element) { + StringBuilder text = new StringBuilder(); + NodeList children = element.getChildNodes(); + + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeType() == Node.TEXT_NODE) { + text.append(child.getTextContent().trim()).append(" "); + } else if (child.getNodeType() == Node.ELEMENT_NODE) { + Element childElement = (Element) child; + if ("p".equals(childElement.getLocalName())) { + text.append(childElement.getTextContent().trim()).append("\n\n"); + } else { + text.append(extractTextFromElement(childElement)); + } + } + } + + return text.toString().trim(); + } + + private LocalDate parseDate(String dateString) { + if (StringUtils.isBlank(dateString)) { + return null; + } + + try { + // Try parsing ISO date format (YYYY-MM-DD) + if (dateString.matches("\\d{4}-\\d{2}-\\d{2}")) { + return LocalDate.parse(dateString); + } + + // Try extracting year only + Matcher matcher = YEAR_PATTERN.matcher(dateString); + if (matcher.find()) { + int year = Integer.parseInt(matcher.group()); + return LocalDate.of(year, 1, 1); + } + } catch (Exception e) { + log.debug("Failed to parse date: {}", dateString, e); + } + + return null; + } + + private Element getFirstElementByTagNameNS(Node parent, String namespace, String localName) { + NodeList nodes; + if (parent instanceof Document) { + nodes = ((Document) parent).getElementsByTagNameNS(namespace, localName); + } else if (parent instanceof Element) { + nodes = ((Element) parent).getElementsByTagNameNS(namespace, localName); + } else { + return null; + } + return nodes.getLength() > 0 ? (Element) nodes.item(0) : null; + } + + private InputStream getInputStream(File file) throws Exception { + FileInputStream fis = new FileInputStream(file); + + // Check if file is gzipped (FB2 files can be .fb2 or .fb2.zip/.fb2.gz) + if (file.getName().toLowerCase().endsWith(".gz")) { + return new GZIPInputStream(fis); + } + + return fis; + } +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/extractor/MetadataExtractorFactory.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/extractor/MetadataExtractorFactory.java index 30b6ed05..54ccf587 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/extractor/MetadataExtractorFactory.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/metadata/extractor/MetadataExtractorFactory.java @@ -15,12 +15,14 @@ public class MetadataExtractorFactory { private final EpubMetadataExtractor epubMetadataExtractor; private final PdfMetadataExtractor pdfMetadataExtractor; private final CbxMetadataExtractor cbxMetadataExtractor; + private final Fb2MetadataExtractor fb2MetadataExtractor; public BookMetadata extractMetadata(BookFileType bookFileType, File file) { return switch (bookFileType) { case PDF -> pdfMetadataExtractor.extractMetadata(file); case EPUB -> epubMetadataExtractor.extractMetadata(file); case CBX -> cbxMetadataExtractor.extractMetadata(file); + case FB2 -> fb2MetadataExtractor.extractMetadata(file); }; } @@ -29,6 +31,7 @@ public class MetadataExtractorFactory { case PDF -> pdfMetadataExtractor.extractMetadata(file); case EPUB -> epubMetadataExtractor.extractMetadata(file); case CBZ, CBR, CB7 -> cbxMetadataExtractor.extractMetadata(file); + case FB2 -> fb2MetadataExtractor.extractMetadata(file); }; } @@ -37,6 +40,7 @@ public class MetadataExtractorFactory { case EPUB -> epubMetadataExtractor.extractCover(file); case PDF -> pdfMetadataExtractor.extractCover(file); case CBZ, CBR, CB7 -> cbxMetadataExtractor.extractCover(file); + case FB2 -> fb2MetadataExtractor.extractCover(file); }; } } diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/service/metadata/extractor/Fb2MetadataExtractorTest.java b/booklore-api/src/test/java/com/adityachandel/booklore/service/metadata/extractor/Fb2MetadataExtractorTest.java new file mode 100644 index 00000000..fe032988 --- /dev/null +++ b/booklore-api/src/test/java/com/adityachandel/booklore/service/metadata/extractor/Fb2MetadataExtractorTest.java @@ -0,0 +1,885 @@ +package com.adityachandel.booklore.service.metadata.extractor; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.*; + +class Fb2MetadataExtractorTest { + + private static final String DEFAULT_TITLE = "The Seven Poor Travellers"; + private static final String DEFAULT_AUTHOR_FIRST = "Charles"; + private static final String DEFAULT_AUTHOR_LAST = "Dickens"; + private static final String DEFAULT_AUTHOR_FULL = "Charles Dickens"; + private static final String DEFAULT_GENRE = "antique"; + private static final String DEFAULT_LANGUAGE = "ru"; + private static final String DEFAULT_PUBLISHER = "Test Publisher"; + private static final String DEFAULT_ISBN = "9781234567890"; + private static final String DEFAULT_SERIES = "Great Works"; + + private Fb2MetadataExtractor extractor; + + @TempDir + Path tempDir; + + @BeforeEach + void setUp() { + extractor = new Fb2MetadataExtractor(); + } + + @Nested + @DisplayName("Basic Metadata Extraction Tests") + class BasicMetadataTests { + + @Test + @DisplayName("Should extract title from title-info") + void extractMetadata_withTitle_returnsTitle() throws IOException { + String fb2Content = createFb2WithTitleInfo( + DEFAULT_TITLE, + DEFAULT_AUTHOR_FIRST, + DEFAULT_AUTHOR_LAST, + DEFAULT_GENRE, + DEFAULT_LANGUAGE + ); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(DEFAULT_TITLE, result.getTitle()); + } + + @Test + @DisplayName("Should extract author name from title-info") + void extractMetadata_withAuthor_returnsAuthor() throws IOException { + String fb2Content = createFb2WithTitleInfo( + DEFAULT_TITLE, + DEFAULT_AUTHOR_FIRST, + DEFAULT_AUTHOR_LAST, + DEFAULT_GENRE, + DEFAULT_LANGUAGE + ); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertNotNull(result.getAuthors()); + assertEquals(1, result.getAuthors().size()); + assertTrue(result.getAuthors().contains(DEFAULT_AUTHOR_FULL)); + } + + @Test + @DisplayName("Should extract multiple authors") + void extractMetadata_withMultipleAuthors_returnsAllAuthors() throws IOException { + String fb2Content = createFb2WithMultipleAuthors(); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertNotNull(result.getAuthors()); + assertEquals(2, result.getAuthors().size()); + assertTrue(result.getAuthors().contains("Charles Dickens")); + assertTrue(result.getAuthors().contains("Jane Austen")); + } + + @Test + @DisplayName("Should extract genre as category") + void extractMetadata_withGenre_returnsCategory() throws IOException { + String fb2Content = createFb2WithTitleInfo( + DEFAULT_TITLE, + DEFAULT_AUTHOR_FIRST, + DEFAULT_AUTHOR_LAST, + DEFAULT_GENRE, + DEFAULT_LANGUAGE + ); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertNotNull(result.getCategories()); + assertTrue(result.getCategories().contains(DEFAULT_GENRE)); + } + + @Test + @DisplayName("Should extract multiple genres as categories") + void extractMetadata_withMultipleGenres_returnsAllCategories() throws IOException { + String fb2Content = createFb2WithMultipleGenres(); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertNotNull(result.getCategories()); + assertTrue(result.getCategories().contains("fiction")); + assertTrue(result.getCategories().contains("drama")); + } + + @Test + @DisplayName("Should extract language") + void extractMetadata_withLanguage_returnsLanguage() throws IOException { + String fb2Content = createFb2WithTitleInfo( + DEFAULT_TITLE, + DEFAULT_AUTHOR_FIRST, + DEFAULT_AUTHOR_LAST, + DEFAULT_GENRE, + DEFAULT_LANGUAGE + ); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(DEFAULT_LANGUAGE, result.getLanguage()); + } + + @Test + @DisplayName("Should extract annotation as description") + void extractMetadata_withAnnotation_returnsDescription() throws IOException { + String annotation = "This is a test book description"; + String fb2Content = createFb2WithAnnotation(annotation); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertNotNull(result.getDescription()); + assertTrue(result.getDescription().contains(annotation)); + } + } + + @Nested + @DisplayName("Date Extraction Tests") + class DateExtractionTests { + + @Test + @DisplayName("Should extract date from title-info") + void extractMetadata_withDate_returnsDate() throws IOException { + String fb2Content = createFb2WithDate("2024-06-15"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(LocalDate.of(2024, 6, 15), result.getPublishedDate()); + } + + @Test + @DisplayName("Should extract year-only date") + void extractMetadata_withYearOnly_returnsDateWithJanuary1st() throws IOException { + String fb2Content = createFb2WithDate("2024"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(LocalDate.of(2024, 1, 1), result.getPublishedDate()); + } + + @Test + @DisplayName("Should handle date with value attribute") + void extractMetadata_withDateValue_returnsDate() throws IOException { + String fb2Content = createFb2WithDateValue("2024-06-15", "June 15, 2024"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(LocalDate.of(2024, 6, 15), result.getPublishedDate()); + } + } + + @Nested + @DisplayName("Series Metadata Tests") + class SeriesMetadataTests { + + @Test + @DisplayName("Should extract series name from sequence") + void extractMetadata_withSequence_returnsSeriesName() throws IOException { + String fb2Content = createFb2WithSequence(DEFAULT_SERIES, "3"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(DEFAULT_SERIES, result.getSeriesName()); + } + + @Test + @DisplayName("Should extract series number from sequence") + void extractMetadata_withSequence_returnsSeriesNumber() throws IOException { + String fb2Content = createFb2WithSequence(DEFAULT_SERIES, "3"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(3.0f, result.getSeriesNumber(), 0.001); + } + + @Test + @DisplayName("Should handle decimal series numbers") + void extractMetadata_withDecimalSequence_returnsDecimalSeriesNumber() throws IOException { + String fb2Content = createFb2WithSequence(DEFAULT_SERIES, "2.5"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(2.5f, result.getSeriesNumber(), 0.001); + } + } + + @Nested + @DisplayName("Publisher Info Extraction Tests") + class PublisherInfoTests { + + @Test + @DisplayName("Should extract publisher from publish-info") + void extractMetadata_withPublisher_returnsPublisher() throws IOException { + String fb2Content = createFb2WithPublishInfo(DEFAULT_PUBLISHER, "2024", DEFAULT_ISBN); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(DEFAULT_PUBLISHER, result.getPublisher()); + } + + @Test + @DisplayName("Should extract year from publish-info") + void extractMetadata_withPublishYear_returnsDate() throws IOException { + String fb2Content = createFb2WithPublishInfo(DEFAULT_PUBLISHER, "2024", null); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals(LocalDate.of(2024, 1, 1), result.getPublishedDate()); + } + + @Test + @DisplayName("Should extract ISBN-13 from publish-info") + void extractMetadata_withIsbn13_returnsIsbn13() throws IOException { + String fb2Content = createFb2WithPublishInfo(null, null, "9781234567890"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals("9781234567890", result.getIsbn13()); + } + + @Test + @DisplayName("Should extract ISBN-10 from publish-info") + void extractMetadata_withIsbn10_returnsIsbn10() throws IOException { + String fb2Content = createFb2WithPublishInfo(null, null, "1234567890"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertEquals("1234567890", result.getIsbn10()); + } + } + + @Nested + @DisplayName("Keywords Extraction Tests") + class KeywordsTests { + + @Test + @DisplayName("Should extract keywords as categories") + void extractMetadata_withKeywords_returnsCategories() throws IOException { + String fb2Content = createFb2WithKeywords("adventure, mystery, thriller"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertNotNull(result.getCategories()); + assertTrue(result.getCategories().contains("adventure")); + assertTrue(result.getCategories().contains("mystery")); + assertTrue(result.getCategories().contains("thriller")); + } + + @Test + @DisplayName("Should handle keywords with semicolon separator") + void extractMetadata_withSemicolonKeywords_returnsCategories() throws IOException { + String fb2Content = createFb2WithKeywords("adventure; mystery; thriller"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertNotNull(result.getCategories()); + assertTrue(result.getCategories().contains("adventure")); + assertTrue(result.getCategories().contains("mystery")); + assertTrue(result.getCategories().contains("thriller")); + } + } + + @Nested + @DisplayName("Author Name Extraction Tests") + class AuthorNameTests { + + @Test + @DisplayName("Should extract author with first and last name") + void extractMetadata_withFirstAndLastName_returnsFullName() throws IOException { + String fb2Content = createFb2WithAuthorNames("John", null, "Doe", null); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertTrue(result.getAuthors().contains("John Doe")); + } + + @Test + @DisplayName("Should extract author with first, middle and last name") + void extractMetadata_withMiddleName_returnsFullNameWithMiddle() throws IOException { + String fb2Content = createFb2WithAuthorNames("John", "Robert", "Doe", null); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertTrue(result.getAuthors().contains("John Robert Doe")); + } + + @Test + @DisplayName("Should use nickname when name parts are missing") + void extractMetadata_withNicknameOnly_returnsNickname() throws IOException { + String fb2Content = createFb2WithAuthorNames(null, null, null, "WriterPro"); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertNotNull(result); + assertTrue(result.getAuthors().contains("WriterPro")); + } + } + + @Nested + @DisplayName("Cover Extraction Tests") + class CoverExtractionTests { + + @Test + @DisplayName("Should extract cover image from binary section") + void extractCover_withCoverImage_returnsCoverBytes() throws IOException { + byte[] imageData = createMinimalPngImage(); + String fb2Content = createFb2WithCover(imageData); + File fb2File = createFb2File(fb2Content); + + byte[] result = extractor.extractCover(fb2File); + + assertNotNull(result); + assertTrue(result.length > 0); + } + + @Test + @DisplayName("Should return null when no cover present") + void extractCover_noCover_returnsNull() throws IOException { + String fb2Content = createMinimalFb2(); + File fb2File = createFb2File(fb2Content); + + byte[] result = extractor.extractCover(fb2File); + + assertNull(result); + } + } + + @Nested + @DisplayName("Complete Metadata Extraction Test") + class CompleteMetadataTest { + + @Test + @DisplayName("Should extract all metadata fields from complete FB2 with title-info") + void extractMetadata_completeFile_extractsAllFields() throws IOException { + String fb2Content = createCompleteFb2(); + File fb2File = createFb2File(fb2Content); + + BookMetadata result = extractor.extractMetadata(fb2File); + + assertAll( + () -> assertNotNull(result, "Metadata should not be null"), + () -> assertEquals("Pride and Prejudice", result.getTitle(), "Title should be extracted"), + () -> assertNotNull(result.getAuthors(), "Authors should not be null"), + () -> assertEquals(1, result.getAuthors().size(), "Should have one author"), + () -> assertTrue(result.getAuthors().contains("Jane Austen"), "Should contain full author name"), + () -> assertNotNull(result.getCategories(), "Categories should not be null"), + () -> assertTrue(result.getCategories().contains("romance"), "Should contain genre"), + () -> assertEquals("en", result.getLanguage(), "Language should be extracted"), + () -> assertNotNull(result.getDescription(), "Description should not be null"), + () -> assertTrue(result.getDescription().contains("classic novel"), "Description should contain annotation text"), + () -> assertEquals(LocalDate.of(1813, 1, 1), result.getPublishedDate(), "Published date should be extracted"), + () -> assertEquals("T. Egerton", result.getPublisher(), "Publisher should be extracted"), + () -> assertEquals("Classic Literature Series", result.getSeriesName(), "Series name should be extracted"), + () -> assertEquals(2.0f, result.getSeriesNumber(), 0.001, "Series number should be extracted") + ); + } + + private String createCompleteFb2() { + return """ + + + + + romance + + Jane + Austen + + Pride and Prejudice + +

Pride and Prejudice is a classic novel by Jane Austen, first published in 1813. It is a romantic novel of manners that follows the character development of Elizabeth Bennet.

+

The novel deals with issues of morality, education, and marriage in the society of the landed gentry of the British Regency. Elizabeth must learn the error of making hasty judgments and come to appreciate the difference between superficial goodness and actual goodness.

+
+ romance, regency, england, bennet, darcy, marriage + 1813 + en + +
+ + + TestUser + + January 1, 2024 + TestUser_PrideAndPrejudice_12345 + 2.0 + + + Pride and Prejudice + T. Egerton + London + 1813 + +
+ +
+ + <p>Chapter 1</p> + +

It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.

+
+ +
+ """; + } + } + + @Nested + @DisplayName("Edge Cases and Error Handling") + class EdgeCaseTests { + + @Test + @DisplayName("Should handle empty FB2 file gracefully") + void extractMetadata_emptyFile_returnsNull() throws IOException { + File emptyFile = tempDir.resolve("empty.fb2").toFile(); + try (FileOutputStream fos = new FileOutputStream(emptyFile)) { + fos.write("".getBytes(StandardCharsets.UTF_8)); + } + + BookMetadata result = extractor.extractMetadata(emptyFile); + + assertNull(result); + } + + @Test + @DisplayName("Should handle invalid XML gracefully") + void extractMetadata_invalidXml_returnsNull() throws IOException { + File invalidFile = tempDir.resolve("invalid.fb2").toFile(); + try (FileOutputStream fos = new FileOutputStream(invalidFile)) { + fos.write("this is not valid XML".getBytes(StandardCharsets.UTF_8)); + } + + BookMetadata result = extractor.extractMetadata(invalidFile); + + assertNull(result); + } + + @Test + @DisplayName("Should handle non-existent file gracefully") + void extractMetadata_nonExistentFile_returnsNull() { + File nonExistent = new File(tempDir.toFile(), "does-not-exist.fb2"); + + BookMetadata result = extractor.extractMetadata(nonExistent); + + assertNull(result); + } + } + + // Helper methods to create FB2 test files + + private String createMinimalFb2() { + return """ + + + + + fiction + + Test + Author + + Test Book + en + + + +
+

Test content

+
+ +
+ """; + } + + private String createFb2WithTitleInfo(String title, String firstName, String lastName, String genre, String lang) { + return String.format(""" + + + + + %s + + %s + %s + + %s + %s + + + +
+

Content

+
+ +
+ """, genre, firstName, lastName, title, lang); + } + + private String createFb2WithMultipleAuthors() { + return """ + + + + + fiction + + Charles + Dickens + + + Jane + Austen + + Collaborative Work + en + + + +
+

Content

+
+ +
+ """; + } + + private String createFb2WithMultipleGenres() { + return """ + + + + + fiction + drama + + Test + Author + + Multi-Genre Book + en + + + +
+

Content

+
+ +
+ """; + } + + private String createFb2WithAnnotation(String annotation) { + return String.format(""" + + + + + fiction + + Test + Author + + Book with Annotation + +

%s

+
+ en +
+
+ +
+

Content

+
+ +
+ """, annotation); + } + + private String createFb2WithDate(String date) { + return String.format(""" + + + + + fiction + + Test + Author + + Book with Date + %s + en + + + +
+

Content

+
+ +
+ """, date); + } + + private String createFb2WithDateValue(String dateValue, String dateText) { + return String.format(""" + + + + + fiction + + Test + Author + + Book with Date Value + %s + en + + + +
+

Content

+
+ +
+ """, dateValue, dateText); + } + + private String createFb2WithSequence(String seriesName, String seriesNumber) { + return String.format(""" + + + + + fiction + + Test + Author + + Book in Series + + en + + + +
+

Content

+
+ +
+ """, seriesName, seriesNumber); + } + + private String createFb2WithPublishInfo(String publisher, String year, String isbn) { + StringBuilder publishInfo = new StringBuilder(); + if (publisher != null) { + publishInfo.append(String.format(" %s\n", publisher)); + } + if (year != null) { + publishInfo.append(String.format(" %s\n", year)); + } + if (isbn != null) { + publishInfo.append(String.format(" %s\n", isbn)); + } + + return String.format(""" + + + + + fiction + + Test + Author + + Book with Publish Info + en + + + %s + + +
+

Content

+
+ +
+ """, publishInfo); + } + + private String createFb2WithKeywords(String keywords) { + return String.format(""" + + + + + fiction + + Test + Author + + Book with Keywords + %s + en + + + +
+

Content

+
+ +
+ """, keywords); + } + + private String createFb2WithAuthorNames(String firstName, String middleName, String lastName, String nickname) { + StringBuilder authorInfo = new StringBuilder(); + if (firstName != null) { + authorInfo.append(String.format(" %s\n", firstName)); + } + if (middleName != null) { + authorInfo.append(String.format(" %s\n", middleName)); + } + if (lastName != null) { + authorInfo.append(String.format(" %s\n", lastName)); + } + if (nickname != null) { + authorInfo.append(String.format(" %s\n", nickname)); + } + + return String.format(""" + + + + + fiction + + %s + Book with Complex Author + en + + + +
+

Content

+
+ +
+ """, authorInfo); + } + + private String createFb2WithCover(byte[] imageData) { + String base64Image = Base64.getEncoder().encodeToString(imageData); + return String.format(""" + + + + + fiction + + Test + Author + + Book with Cover + + + + en + + + +
+

Content

+
+ + %s +
+ """, base64Image); + } + + private byte[] createMinimalPngImage() { + return new byte[]{ + (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, + 0x00, 0x00, 0x00, 0x0D, + 0x49, 0x48, 0x44, 0x52, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, + 0x08, 0x06, + 0x00, 0x00, 0x00, + (byte) 0x90, (byte) 0x77, (byte) 0x53, (byte) 0xDE, + 0x00, 0x00, 0x00, 0x0A, + 0x49, 0x44, 0x41, 0x54, + 0x78, (byte) 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, + 0x00, 0x01, + 0x0D, (byte) 0x0A, 0x2D, (byte) 0xB4, + 0x00, 0x00, 0x00, 0x00, + 0x49, 0x45, 0x4E, 0x44, + (byte) 0xAE, 0x42, 0x60, (byte) 0x82 + }; + } + + private File createFb2File(String content) throws IOException { + File fb2File = tempDir.resolve("test-" + System.nanoTime() + ".fb2").toFile(); + try (FileOutputStream fos = new FileOutputStream(fb2File)) { + fos.write(content.getBytes(StandardCharsets.UTF_8)); + } + return fb2File; + } +} diff --git a/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.html b/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.html index b101a052..bf9c01d7 100644 --- a/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.html +++ b/booklore-ui/src/app/features/book/components/book-browser/book-card/book-card.component.html @@ -38,7 +38,7 @@ } - + @if (isCheckboxEnabled) { } } - @if (book!.bookType !== 'PDF') { + @if (book!.bookType !== 'PDF' && book!.bookType !== 'FB2') { } diff --git a/booklore-ui/src/app/shared/components/book-uploader/book-uploader.component.html b/booklore-ui/src/app/shared/components/book-uploader/book-uploader.component.html index f743ff0b..31e38c6f 100644 --- a/booklore-ui/src/app/shared/components/book-uploader/book-uploader.component.html +++ b/booklore-ui/src/app/shared/components/book-uploader/book-uploader.component.html @@ -89,7 +89,7 @@ [maxFileSize]="maxFileSizeBytes" [customUpload]="true" [multiple]="true" - accept=".pdf,.epub,.cbz,.cbr,.cb7" + accept=".pdf,.epub,.cbz,.cbr,.cb7,.fb2" (onSelect)="onFilesSelect($event)" (uploadHandler)="uploadFiles($event)" [disabled]="value === 'library' ? (!selectedLibrary || !selectedPath) : false"> @@ -176,7 +176,7 @@

Drag and Drop Files

- Supported formats: .pdf, .epub, .cbz, .cbr, .cb7 + Supported formats: .pdf, .epub, .cbz, .cbr, .cb7, .fb2
Maximum file size: 100 MB per file

From 9da13ae81806fcb9edec5616a9d430c99e3e9dbe Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Date: Sun, 14 Dec 2025 09:53:19 +0530 Subject: [PATCH 09/46] Feat/conversion CBX to EPUB compression configuration (#1844) * feat(conversion): add image compression percentage setting for CBX to EPUB conversion * feat(conversion): add conversion image compression setting to kobo sync settings frontend --- .../model/dto/settings/KoboSettings.java | 1 + .../appsettings/SettingPersistenceHelper.java | 1 + .../service/book/BookDownloadService.java | 3 ++- .../service/kobo/CbxConversionService.java | 18 ++++++++-------- .../kobo/CbxConversionIntegrationTest.java | 2 +- .../kobo/CbxConversionServiceTest.java | 16 +++++++------- .../kobo-sync-settings-component.html | 21 ++++++++++++++++++- .../kobo-sync-settings-component.ts | 2 ++ .../app/shared/model/app-settings.model.ts | 1 + 9 files changed, 45 insertions(+), 20 deletions(-) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/settings/KoboSettings.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/settings/KoboSettings.java index 1a6f1138..47c0e1f2 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/settings/KoboSettings.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/settings/KoboSettings.java @@ -11,4 +11,5 @@ public class KoboSettings { private boolean convertCbxToEpub; private int conversionLimitInMbForCbx; private boolean forceEnableHyphenation; + private int conversionImageCompressionPercentage; } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/SettingPersistenceHelper.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/SettingPersistenceHelper.java index 9548deee..d1a0a6b1 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/SettingPersistenceHelper.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/appsettings/SettingPersistenceHelper.java @@ -255,6 +255,7 @@ public class SettingPersistenceHelper { .conversionLimitInMb(100) .convertCbxToEpub(false) .conversionLimitInMbForCbx(100) + .conversionImageCompressionPercentage(85) .forceEnableHyphenation(false) .build(); } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/book/BookDownloadService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/book/BookDownloadService.java index a5b93542..3ba1ae69 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/book/BookDownloadService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/book/BookDownloadService.java @@ -96,6 +96,7 @@ public class BookDownloadService { boolean convertEpubToKepub = isEpub && koboSettings.isConvertToKepub() && bookEntity.getFileSizeKb() <= (long) koboSettings.getConversionLimitInMb() * 1024; boolean convertCbxToEpub = isCbx && koboSettings.isConvertCbxToEpub() && bookEntity.getFileSizeKb() <= (long) koboSettings.getConversionLimitInMbForCbx() * 1024; + int compressionPercentage = koboSettings.getConversionImageCompressionPercentage(); Path tempDir = null; try { File inputFile = new File(FileUtils.getBookFullPath(bookEntity)); @@ -106,7 +107,7 @@ public class BookDownloadService { } if (convertCbxToEpub) { - fileToSend = cbxConversionService.convertCbxToEpub(inputFile, tempDir.toFile(), bookEntity); + fileToSend = cbxConversionService.convertCbxToEpub(inputFile, tempDir.toFile(), bookEntity,compressionPercentage); } if (convertEpubToKepub) { diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/kobo/CbxConversionService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/kobo/CbxConversionService.java index 0215559d..31a3ad51 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/kobo/CbxConversionService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/kobo/CbxConversionService.java @@ -102,20 +102,20 @@ public class CbxConversionService { * @throws IllegalArgumentException if the file format is not supported * @throws IllegalStateException if no valid images are found in the archive */ - public File convertCbxToEpub(File cbxFile, File tempDir, BookEntity bookEntity) + public File convertCbxToEpub(File cbxFile, File tempDir, BookEntity bookEntity, int compressionPercentage) throws IOException, TemplateException, RarException { validateInputs(cbxFile, tempDir); log.info("Starting CBX to EPUB conversion for: {}", cbxFile.getName()); - File outputFile = executeCbxConversion(cbxFile, tempDir, bookEntity); + File outputFile = executeCbxConversion(cbxFile, tempDir, bookEntity,compressionPercentage); log.info("Successfully converted {} to {} (size: {} bytes)", cbxFile.getName(), outputFile.getName(), outputFile.length()); return outputFile; } - private File executeCbxConversion(File cbxFile, File tempDir, BookEntity bookEntity) + private File executeCbxConversion(File cbxFile, File tempDir, BookEntity bookEntity,int compressionPercentage) throws IOException, TemplateException, RarException { Path epubFilePath = Paths.get(tempDir.getAbsolutePath(), cbxFile.getName() + ".epub"); @@ -136,7 +136,7 @@ public class CbxConversionService { addMetaInfContainer(zipOut); addStylesheet(zipOut); - List contentGroups = addImagesAndPages(zipOut, imagePaths); + List contentGroups = addImagesAndPages(zipOut, imagePaths,compressionPercentage); addContentOpf(zipOut, bookEntity, contentGroups); addTocNcx(zipOut, bookEntity, contentGroups); @@ -340,13 +340,13 @@ public class CbxConversionService { zipOut.closeArchiveEntry(); } - private List addImagesAndPages(ZipArchiveOutputStream zipOut, List imagePaths) + private List addImagesAndPages(ZipArchiveOutputStream zipOut, List imagePaths,int compressionPercentage) throws IOException, TemplateException { List contentGroups = new ArrayList<>(); if (!imagePaths.isEmpty()) { - addImageToZipFromPath(zipOut, COVER_IMAGE_PATH, imagePaths.getFirst()); + addImageToZipFromPath(zipOut, COVER_IMAGE_PATH, imagePaths.getFirst(),compressionPercentage); } for (int i = 0; i < imagePaths.size(); i++) { @@ -358,7 +358,7 @@ public class CbxConversionService { String imagePath = IMAGE_ROOT_PATH + imageFileName; String htmlPath = HTML_ROOT_PATH + htmlFileName; - addImageToZipFromPath(zipOut, imagePath, imageSourcePath); + addImageToZipFromPath(zipOut, imagePath, imageSourcePath,compressionPercentage); String htmlContent = generatePageHtml(imageFileName, i + 1); ZipArchiveEntry htmlEntry = new ZipArchiveEntry(htmlPath); @@ -372,7 +372,7 @@ public class CbxConversionService { return contentGroups; } - private void addImageToZipFromPath(ZipArchiveOutputStream zipOut, String epubImagePath, Path sourceImagePath) + private void addImageToZipFromPath(ZipArchiveOutputStream zipOut, String epubImagePath, Path sourceImagePath,int compressionPercentage) throws IOException { ZipArchiveEntry imageEntry = new ZipArchiveEntry(epubImagePath); zipOut.putArchiveEntry(imageEntry); @@ -385,7 +385,7 @@ public class CbxConversionService { try (InputStream fis = Files.newInputStream(sourceImagePath)) { BufferedImage image = ImageIO.read(fis); if (image != null) { - writeJpegImage(image, zipOut, 0.85f); + writeJpegImage(image, zipOut, compressionPercentage/100f); } else { log.warn("Could not decode image {}, copying raw bytes", sourceImagePath.getFileName()); try (InputStream rawStream = Files.newInputStream(sourceImagePath)) { diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/service/kobo/CbxConversionIntegrationTest.java b/booklore-api/src/test/java/com/adityachandel/booklore/service/kobo/CbxConversionIntegrationTest.java index 26c5fbd1..6910f99c 100644 --- a/booklore-api/src/test/java/com/adityachandel/booklore/service/kobo/CbxConversionIntegrationTest.java +++ b/booklore-api/src/test/java/com/adityachandel/booklore/service/kobo/CbxConversionIntegrationTest.java @@ -42,7 +42,7 @@ class CbxConversionIntegrationTest { File testCbzFile = createTestComicCbzFile(); BookEntity bookMetadata = createTestBookMetadata(); - File epubFile = conversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), bookMetadata); + File epubFile = conversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), bookMetadata,85); assertThat(epubFile) .exists() diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/service/kobo/CbxConversionServiceTest.java b/booklore-api/src/test/java/com/adityachandel/booklore/service/kobo/CbxConversionServiceTest.java index dfbad12a..ed4d4170 100644 --- a/booklore-api/src/test/java/com/adityachandel/booklore/service/kobo/CbxConversionServiceTest.java +++ b/booklore-api/src/test/java/com/adityachandel/booklore/service/kobo/CbxConversionServiceTest.java @@ -42,7 +42,7 @@ class CbxConversionServiceTest { @Test void convertCbxToEpub_WithValidCbzFile_ShouldGenerateValidEpub() throws IOException, TemplateException, RarException { - File epubFile = cbxConversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), testBookEntity); + File epubFile = cbxConversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), testBookEntity,85); assertThat(epubFile).exists(); assertThat(epubFile.getName()).endsWith(".epub"); @@ -53,7 +53,7 @@ class CbxConversionServiceTest { @Test void convertCbxToEpub_WithNullCbxFile_ShouldThrowException() { - assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(null, tempDir.toFile(), testBookEntity)) + assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(null, tempDir.toFile(), testBookEntity,85)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Invalid CBX file"); } @@ -62,7 +62,7 @@ class CbxConversionServiceTest { void convertCbxToEpub_WithNonExistentFile_ShouldThrowException() { File nonExistentFile = new File(tempDir.toFile(), "non-existent.cbz"); - assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(nonExistentFile, tempDir.toFile(), testBookEntity)) + assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(nonExistentFile, tempDir.toFile(), testBookEntity,85)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Invalid CBX file"); } @@ -71,14 +71,14 @@ class CbxConversionServiceTest { void convertCbxToEpub_WithUnsupportedFileFormat_ShouldThrowException() throws IOException { File unsupportedFile = Files.createFile(tempDir.resolve("test.txt")).toFile(); - assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(unsupportedFile, tempDir.toFile(), testBookEntity)) + assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(unsupportedFile, tempDir.toFile(), testBookEntity,85)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Unsupported file format"); } @Test void convertCbxToEpub_WithNullTempDir_ShouldThrowException() { - assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(testCbzFile, null, testBookEntity)) + assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(testCbzFile, null, testBookEntity,85)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Invalid temp directory"); } @@ -87,7 +87,7 @@ class CbxConversionServiceTest { void convertCbxToEpub_WithEmptyCbzFile_ShouldThrowException() throws IOException { File emptyCbzFile = createEmptyCbzFile(); - assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(emptyCbzFile, tempDir.toFile(), testBookEntity)) + assertThatThrownBy(() -> cbxConversionService.convertCbxToEpub(emptyCbzFile, tempDir.toFile(), testBookEntity,85)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("No valid images found"); } @@ -118,7 +118,7 @@ class CbxConversionServiceTest { @Test void convertCbxToEpub_WithNullBookEntity_ShouldUseDefaultMetadata() throws IOException, TemplateException, RarException { - File epubFile = cbxConversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), null); + File epubFile = cbxConversionService.convertCbxToEpub(testCbzFile, tempDir.toFile(), null,85); assertThat(epubFile).exists(); verifyEpubStructure(epubFile); @@ -128,7 +128,7 @@ class CbxConversionServiceTest { void convertCbxToEpub_WithMultipleImages_ShouldPreservePageOrder() throws IOException, TemplateException, RarException { File multiPageCbzFile = createMultiPageCbzFile(); - File epubFile = cbxConversionService.convertCbxToEpub(multiPageCbzFile, tempDir.toFile(), testBookEntity); + File epubFile = cbxConversionService.convertCbxToEpub(multiPageCbzFile, tempDir.toFile(), testBookEntity,85); assertThat(epubFile).exists(); verifyPageOrderInEpub(epubFile, 5); diff --git a/booklore-ui/src/app/features/settings/device-settings/component/kobo-sync-settings/kobo-sync-settings-component.html b/booklore-ui/src/app/features/settings/device-settings/component/kobo-sync-settings/kobo-sync-settings-component.html index d3631097..134a7328 100644 --- a/booklore-ui/src/app/features/settings/device-settings/component/kobo-sync-settings/kobo-sync-settings-component.html +++ b/booklore-ui/src/app/features/settings/device-settings/component/kobo-sync-settings/kobo-sync-settings-component.html @@ -263,7 +263,26 @@

- +
+
+
+ +
+ + +
+
+

+ Comic book conversions can sometimes result in very large files. This setting allows you to compress the images during conversion to prevent size from shooting up. +

+
+
diff --git a/booklore-ui/src/app/features/settings/device-settings/component/kobo-sync-settings/kobo-sync-settings-component.ts b/booklore-ui/src/app/features/settings/device-settings/component/kobo-sync-settings/kobo-sync-settings-component.ts index 0847e345..15163e3d 100644 --- a/booklore-ui/src/app/features/settings/device-settings/component/kobo-sync-settings/kobo-sync-settings-component.ts +++ b/booklore-ui/src/app/features/settings/device-settings/component/kobo-sync-settings/kobo-sync-settings-component.ts @@ -48,6 +48,7 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy { convertToKepub: false, conversionLimitInMb: 100, convertCbxToEpub: false, + conversionImageCompressionPercentage: 85, conversionLimitInMbForCbx: 100, forceEnableHyphenation: false }; @@ -138,6 +139,7 @@ export class KoboSyncSettingsComponent implements OnInit, OnDestroy { this.koboSettings.convertCbxToEpub = settings?.koboSettings?.convertCbxToEpub ?? false; this.koboSettings.conversionLimitInMbForCbx = settings?.koboSettings?.conversionLimitInMbForCbx ?? 100; this.koboSettings.forceEnableHyphenation = settings?.koboSettings?.forceEnableHyphenation ?? false; + this.koboSettings.conversionImageCompressionPercentage = settings?.koboSettings?.conversionImageCompressionPercentage ?? 85; }); } diff --git a/booklore-ui/src/app/shared/model/app-settings.model.ts b/booklore-ui/src/app/shared/model/app-settings.model.ts index ab1b51c3..13168f50 100644 --- a/booklore-ui/src/app/shared/model/app-settings.model.ts +++ b/booklore-ui/src/app/shared/model/app-settings.model.ts @@ -102,6 +102,7 @@ export interface PublicReviewSettings { export interface KoboSettings { convertToKepub: boolean; conversionLimitInMb: number; + conversionImageCompressionPercentage: number; convertCbxToEpub: boolean; conversionLimitInMbForCbx: number; forceEnableHyphenation: boolean; From 3b839d06ba337ec2cccaaef8e926656f13de78ca Mon Sep 17 00:00:00 2001 From: CounterClops <19741838+CounterClops@users.noreply.github.com> Date: Sun, 14 Dec 2025 13:46:30 +0800 Subject: [PATCH 10/46] fix: remove the personal rating extraction from fb2 books (#1856) --- .../booklore/service/fileprocessor/Fb2Processor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/fileprocessor/Fb2Processor.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/fileprocessor/Fb2Processor.java index 467ff3a2..c166b6be 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/fileprocessor/Fb2Processor.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/fileprocessor/Fb2Processor.java @@ -108,7 +108,6 @@ public class Fb2Processor extends AbstractFileProcessor implements BookFileProce metadata.setLanguage(truncate((lang == null || "UND".equalsIgnoreCase(lang)) ? "en" : lang, 1000)); metadata.setAsin(truncate(fb2Metadata.getAsin(), 20)); - metadata.setPersonalRating(fb2Metadata.getPersonalRating()); metadata.setAmazonRating(fb2Metadata.getAmazonRating()); metadata.setAmazonReviewCount(fb2Metadata.getAmazonReviewCount()); metadata.setGoodreadsId(truncate(fb2Metadata.getGoodreadsId(), 100)); From 95ea371bddee971becb8e511fabd24bb5d6390d8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 23:42:31 -0700 Subject: [PATCH 11/46] chore(deps): update dependency gradle to v8.14.3 (#1858) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 2 +- booklore-api/gradlew | 5 ++++- booklore-api/gradlew.bat | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/booklore-api/gradle/wrapper/gradle-wrapper.jar b/booklore-api/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 35551 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 delta 37256 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 diff --git a/booklore-api/gradle/wrapper/gradle-wrapper.properties b/booklore-api/gradle/wrapper/gradle-wrapper.properties index 002b867c..d4081da4 100644 --- a/booklore-api/gradle/wrapper/gradle-wrapper.properties +++ b/booklore-api/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/booklore-api/gradlew b/booklore-api/gradlew index adff685a..23d15a93 100755 --- a/booklore-api/gradlew +++ b/booklore-api/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright ยฉ 2015 the original authors. +# Copyright ยฉ 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,6 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -171,6 +172,7 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -210,6 +212,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/booklore-api/gradlew.bat b/booklore-api/gradlew.bat index c4bdd3ab..db3a6ac2 100644 --- a/booklore-api/gradlew.bat +++ b/booklore-api/gradlew.bat @@ -70,10 +70,11 @@ goto fail :execute @rem Setup the command line +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 55eddf4063d01d4798c9bec4f39b5d25ed623dc2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 13 Dec 2025 23:42:52 -0700 Subject: [PATCH 12/46] chore(deps): update dependency org.assertj:assertj-core to v3.27.6 (#1859) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- booklore-api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booklore-api/build.gradle b/booklore-api/build.gradle index 49400fee..db0ee10e 100644 --- a/booklore-api/build.gradle +++ b/booklore-api/build.gradle @@ -80,7 +80,7 @@ dependencies { // --- Test Dependencies --- testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.assertj:assertj-core:3.27.3' + testImplementation 'org.assertj:assertj-core:3.27.6' testImplementation "org.mockito:mockito-inline:5.2.0" } From 79c8a61c213f0bc20f7e973288dc4c26e48a8223 Mon Sep 17 00:00:00 2001 From: ACX <8075870+acx10@users.noreply.github.com> Date: Sun, 14 Dec 2025 01:57:32 -0700 Subject: [PATCH 13/46] Add Flyway migration version conflict check in GitHub Actions (#1863) Co-authored-by: acx10 --- .github/scripts/analyze-changes.sh | 70 +++++++++++++++ .github/scripts/check-conflicts.sh | 96 +++++++++++++++++++++ .github/scripts/determine-compare-ref.sh | 41 +++++++++ .github/scripts/validate-versions.sh | 79 +++++++++++++++++ .github/workflows/docker-build-publish.yml | 99 +++++++++++++--------- 5 files changed, 346 insertions(+), 39 deletions(-) create mode 100644 .github/scripts/analyze-changes.sh create mode 100644 .github/scripts/check-conflicts.sh create mode 100644 .github/scripts/determine-compare-ref.sh create mode 100644 .github/scripts/validate-versions.sh diff --git a/.github/scripts/analyze-changes.sh b/.github/scripts/analyze-changes.sh new file mode 100644 index 00000000..1665f224 --- /dev/null +++ b/.github/scripts/analyze-changes.sh @@ -0,0 +1,70 @@ +#!/bin/bash +# Exit immediately if a command exits with a non-zero status. +set -e +# Exit if any command in a pipeline fails, not just the last one. +set -o pipefail + +# Define the path where Flyway migration files are stored. +MIGRATION_PATH="booklore-api/src/main/resources/db/migration" + +# Get ALL changes: Added (A), Modified (M), Renamed (R), Copied (C), Deleted (D) +# for SQL files in the migration path between the comparison ref and the current HEAD. +# The output is saved to a temporary file for further processing. +git diff --name-status --diff-filter=AMRCD $COMPARE_REF...HEAD -- "$MIGRATION_PATH/V*.sql" > /tmp/all_changes.txt + +# The check for no changes is now handled in the workflow. +# If this script runs, it's because changes were detected. + +echo "๐Ÿ“ Migration changes detected:" +# Display the detected changes, indented for readability. +cat /tmp/all_changes.txt | sed 's/^/ /' +echo "" + +# Check for deleted files +# Grep for lines starting with 'D' (Deleted). The '|| true' prevents the script from exiting if no matches are found. +DELETED=$(grep "^D" /tmp/all_changes.txt || true) +if [ -n "$DELETED" ]; then + echo "โŒ ERROR: Deleted migration files detected!" + echo "$DELETED" | sed 's/^/ /' + echo "" + echo "Flyway migrations should NEVER be deleted after being applied." + echo "If you need to revert changes, create a new migration." + exit 1 +fi + +# Check for renamed files +# Grep for lines starting with 'R' (Renamed). +RENAMED=$(grep "^R" /tmp/all_changes.txt || true) +if [ -n "$RENAMED" ]; then + echo "โŒ ERROR: Renamed migration files detected!" + echo "$RENAMED" | sed 's/^/ /' + echo "" + echo "Flyway migrations should NEVER be renamed after being applied." + echo "This will cause issues with migration history tracking." + echo "" + echo "๐Ÿ’ก To fix: Revert the rename and create a new migration file instead." + exit 1 +fi + +# Check for modified files +# Grep for lines starting with 'M' (Modified). +MODIFIED=$(grep "^M" /tmp/all_changes.txt || true) +if [ -n "$MODIFIED" ]; then + echo "โŒ ERROR: Modified migration files detected!" + echo "$MODIFIED" | sed 's/^/ /' + echo "" + echo "Flyway migrations should NEVER be modified after being applied." + echo "This will cause checksum validation failures in environments where it has already been applied." + echo "" + echo "๐Ÿ’ก To fix: Revert the changes and create a new migration file instead." + exit 1 +fi + +# Extract ADDED files for conflict checking in a later step. +# We grep for lines starting with 'A' (Added), then use 'cut' to get just the file path. +# 'touch' ensures the file exists even if there are no added files. +grep "^A" /tmp/all_changes.txt | cut -f2- > /tmp/pr_files.txt || touch /tmp/pr_files.txt + +# Set a GitHub Actions output variable to indicate that migration changes were found. +# This is used by the workflow to decide whether to run subsequent steps. +echo "has_changes=true" >> $GITHUB_OUTPUT diff --git a/.github/scripts/check-conflicts.sh b/.github/scripts/check-conflicts.sh new file mode 100644 index 00000000..34c843d5 --- /dev/null +++ b/.github/scripts/check-conflicts.sh @@ -0,0 +1,96 @@ +#!/bin/bash +# Exit immediately if a command exits with a non-zero status. +set -e +# Exit if any command in a pipeline fails, not just the last one. +set -o pipefail + +# If there are no new versions to check, exit gracefully. +# This file is created by 'validate-versions.sh'. +# This can happen if a PR has changes, but none are new migration files. +if [ ! -s /tmp/versions_pr_unique.txt ]; then + echo "โ„น๏ธ No new migration versions to check for conflicts." + exit 0 +fi + +# Define the path where Flyway migration files are stored. +MIGRATION_PATH="booklore-api/src/main/resources/db/migration" + +echo "๐Ÿ” Fetching migration files from $COMPARE_REF..." + +# Get ALL existing migration files from the comparison ref (e.g., 'develop' or a tag). +# 'git ls-tree' lists the contents of a tree object. +# The output is piped to grep to filter for only Flyway SQL files. +# '|| touch' ensures the temp file exists even if no files are found. +git ls-tree -r --name-only $COMPARE_REF -- "$MIGRATION_PATH/" 2>/dev/null | \ + grep "V.*\.sql$" > /tmp/base_files.txt || touch /tmp/base_files.txt + +# Handle the case where no migration files exist in the base branch. +if [ ! -s /tmp/base_files.txt ]; then + echo "โš ๏ธ No migration files found in $COMPARE_REF" + echo "This might be the first migration or the path has changed." + echo "" + echo "โœ… Skipping version conflict check." + + PR_COUNT=$(wc -l < /tmp/versions_pr_unique.txt) + echo "" + echo "๐Ÿ“Š Migration Summary:" + echo " - Existing migrations in $COMPARE_REF: 0" + echo " - New migrations in this PR: $PR_COUNT" + exit 0 +fi + +echo "๐Ÿ“‹ Found $(wc -l < /tmp/base_files.txt) migration files in $COMPARE_REF" + +# Extract versions from the base files. +# The loop reads each file path, extracts the version number from the filename, +# and appends it to a temporary file. +> /tmp/versions_base.txt +while IFS= read -r file; do + filename=$(basename "$file") + # sed extracts the version number (e.g., 1.0.0) from a filename like 'V1.0.0__description.sql'. + version=$(echo "$filename" | sed -n 's/^V\([0-9.]*\)__.*/\1/p') + [ -n "$version" ] && echo "$version" >> /tmp/versions_base.txt +done < /tmp/base_files.txt + +# Create a file with only unique, sorted version numbers from the base. +sort -u /tmp/versions_base.txt > /tmp/versions_base_unique.txt + +BASE_COUNT=$(wc -l < /tmp/versions_base_unique.txt) +echo "๐Ÿ“Š Found $BASE_COUNT unique versions in $COMPARE_REF" + +# Find conflicts between base versions and versions from NEW PR files. +# 'comm -12' finds lines common to both sorted files. +CONFLICTS=$(comm -12 /tmp/versions_base_unique.txt /tmp/versions_pr_unique.txt) + +# If conflicts are found, report them and exit with an error. +if [ -n "$CONFLICTS" ]; then + echo "โŒ Version conflicts detected!" + echo "" + echo "The following versions from your new migration files already exist in $COMPARE_REF:" + echo "$CONFLICTS" | sed 's/^/ V/' + echo "" + + # Show which files have conflicting versions for easier debugging. + echo "Conflicting files:" + while IFS= read -r version; do + echo " Version V$version exists in:" + grep "V${version}__" /tmp/base_files.txt | xargs -n1 basename | sed 's/^/ BASE: /' + # /tmp/pr_files.txt contains only added files from the PR (from analyze-changes.sh). + grep "V${version}__" /tmp/pr_files.txt | xargs -n1 basename | sed 's/^/ PR: /' + done <<< "$CONFLICTS" + + echo "" + echo "๐Ÿ’ก To fix: Use a version number that doesn't exist in $COMPARE_REF" + exit 1 +fi + +echo "โœ… No version conflicts detected." + +# Get the count of new migrations in the PR. +PR_COUNT=$(wc -l < /tmp/versions_pr_unique.txt) + +# Print a final summary. +echo "" +echo "๐Ÿ“Š Migration Summary:" +echo " - Existing migrations in $COMPARE_REF: $BASE_COUNT" +echo " - New migrations in this PR: $PR_COUNT" diff --git a/.github/scripts/determine-compare-ref.sh b/.github/scripts/determine-compare-ref.sh new file mode 100644 index 00000000..13f8839e --- /dev/null +++ b/.github/scripts/determine-compare-ref.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# Exit immediately if a command exits with a non-zero status. +set -e +# Exit if any command in a pipeline fails, not just the last one. +set -o pipefail + +# The target branch of the pull request (e.g., 'develop', 'master') is passed as the first argument. +TARGET_BRANCH="$1" +echo "๐ŸŽฏ Target branch: $TARGET_BRANCH" + +# Handle cases where the target branch is not specified, such as a direct push to a branch. +if [ -z "$TARGET_BRANCH" ]; then + echo "โš ๏ธ No target branch specified (e.g., a direct push event). Defaulting to compare with 'develop'." + TARGET_BRANCH="develop" +fi + +# Logic to determine the comparison reference based on the target branch. +if [ "$TARGET_BRANCH" = "master" ]; then + # For PRs to 'master', we compare against the latest git tag. + # This is common for release workflows where 'master' only contains tagged releases. + if ! LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null); then + echo "โš ๏ธ No tags found in repository. Skipping conflict check." + # Set output to signal the workflow to stop. + echo "has_ref=false" >> $GITHUB_OUTPUT + exit 0 + fi + echo "๐Ÿ“Œ Comparing against last tag: $LAST_TAG" + # Set the COMPARE_REF environment variable for subsequent steps in the job. + echo "COMPARE_REF=$LAST_TAG" >> $GITHUB_ENV +else + # For all other cases (PRs to 'develop', other feature branches, or direct pushes), + # we compare against the 'develop' branch. + echo "๐Ÿ”„ Comparing against head of develop branch" + # Ensure the local 'develop' branch is up-to-date with the remote. + git fetch origin develop:develop + # Set the COMPARE_REF to the remote develop branch. + echo "COMPARE_REF=origin/develop" >> $GITHUB_ENV +fi + +# Set a GitHub Actions output variable to indicate that a valid comparison ref was found. +echo "has_ref=true" >> $GITHUB_OUTPUT diff --git a/.github/scripts/validate-versions.sh b/.github/scripts/validate-versions.sh new file mode 100644 index 00000000..f5396811 --- /dev/null +++ b/.github/scripts/validate-versions.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# Exit immediately if a command exits with a non-zero status. +set -e +# Exit if any command in a pipeline fails, not just the last one. +set -o pipefail + +# Define the path where Flyway migration files are stored. +MIGRATION_PATH="booklore-api/src/main/resources/db/migration" + +# --- Part 1: Check for duplicate versions within the PR branch itself --- + +# Get ALL migration files in the current HEAD of the PR branch for an internal duplicate check. +find "$MIGRATION_PATH" -type f -name "V*.sql" > /tmp/all_pr_files.txt + +# Check for duplicate versions within the PR branch. This prevents merging a branch +# that contains multiple files with the same version number. +echo "๐Ÿ”Ž Checking for duplicate versions in the branch..." +> /tmp/versions_all_pr.txt +# Loop through all found migration files and extract their version numbers. +while IFS= read -r file; do + filename=$(basename "$file") + # sed extracts the version number (e.g., 1.0.0) from a filename like 'V1.0.0__description.sql'. + version=$(echo "$filename" | sed -n 's/^V\([0-9.]*\)__.*/\1/p') + [ -n "$version" ] && echo "$version" >> /tmp/versions_all_pr.txt +done < /tmp/all_pr_files.txt + +# 'uniq -d' filters for lines that appear more than once in the sorted list. +sort /tmp/versions_all_pr.txt | uniq -d > /tmp/duplicates_in_pr.txt + +# If the duplicates file is not empty, report the error and exit. +if [ -s /tmp/duplicates_in_pr.txt ]; then + echo "โŒ Duplicate migration versions found within the branch!" + echo "" + echo "The following versions are duplicated:" + while IFS= read -r version; do + echo " - Version V$version is used by:" + # Show the conflicting files for easy debugging. + grep "V${version}__" /tmp/all_pr_files.txt | xargs -n1 basename | sed 's/^/ /' + done < /tmp/duplicates_in_pr.txt + echo "" + echo "๐Ÿ’ก To fix: Ensure all migration files have a unique version number." + exit 1 +fi + +echo "โœ… No duplicate versions found within the branch." + +# --- Part 2: Extract versions from NEWLY ADDED files for conflict checking against the base branch --- + +# /tmp/pr_files.txt is created by analyze-changes.sh and contains only ADDED files. +# If the file doesn't exist or is empty, there's nothing to check. +if [ ! -f /tmp/pr_files.txt ] || [ ! -s /tmp/pr_files.txt ]; then + echo "โ„น๏ธ No new migration files to check for conflicts." + # Set output to signal the workflow to skip the conflict check step. + echo "has_versions=false" >> $GITHUB_OUTPUT + exit 0 +fi + +echo "๐Ÿ”Ž Extracting versions from new files..." +> /tmp/versions_pr.txt +# Loop through only the NEWLY ADDED files and extract their versions. +while IFS= read -r file; do + filename=$(basename "$file") + version=$(echo "$filename" | sed -n 's/^V\([0-9.]*\)__.*/\1/p') + [ -n "$version" ] && echo "$version" >> /tmp/versions_pr.txt +done < /tmp/pr_files.txt + +# If no valid versions were extracted from the new files, exit. +if [ ! -s /tmp/versions_pr.txt ]; then + echo "โ„น๏ธ No versions found in new migration files." + echo "has_versions=false" >> $GITHUB_OUTPUT + exit 0 +fi + +# Create a sorted, unique list of versions from the new files. +# This file will be used by 'check-conflicts.sh'. +sort -u /tmp/versions_pr.txt > /tmp/versions_pr_unique.txt + +# Set output to signal that there are new versions to check for conflicts. +echo "has_versions=true" >> $GITHUB_OUTPUT diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index b046d6b9..5f660dde 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -2,11 +2,70 @@ name: Build, Tag, Push, and Release to GitHub Container Registry on: push: + branches: + - 'master' + - 'develop' + pull_request: branches: - '**' jobs: + flyway-conflict-check: + runs-on: ubuntu-latest + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine comparison reference + id: compare_ref + run: | + bash .github/scripts/determine-compare-ref.sh "${{ github.base_ref }}" + + - name: Check for migration changes + id: check_migrations + if: steps.compare_ref.outputs.has_ref == 'true' + run: | + # Check for changes in the migration directory + if git diff --name-only --diff-filter=AMRCD ${{ env.COMPARE_REF }}...HEAD | grep -q "booklore-api/src/main/resources/db/migration/"; then + echo "Migration file changes detected." + echo "has_migrations=true" >> $GITHUB_OUTPUT + else + echo "No migration file changes detected. Skipping conflict check." + echo "has_migrations=false" >> $GITHUB_OUTPUT + fi + + - name: Analyze migration changes + if: steps.check_migrations.outputs.has_migrations == 'true' + id: analyze_changes + env: + COMPARE_REF: ${{ env.COMPARE_REF }} + run: | + bash .github/scripts/analyze-changes.sh + + - name: Extract and validate versions + if: steps.check_migrations.outputs.has_migrations == 'true' + id: validate_versions + run: | + bash .github/scripts/validate-versions.sh + + - name: Check for version conflicts with base + if: steps.validate_versions.outputs.has_versions == 'true' + env: + COMPARE_REF: ${{ env.COMPARE_REF }} + run: | + bash .github/scripts/check-conflicts.sh + + - name: Cleanup + if: always() && steps.check_migrations.outputs.has_migrations == 'true' + run: | + rm -f /tmp/all_changes.txt /tmp/pr_files.txt /tmp/base_files.txt + rm -f /tmp/versions_*.txt /tmp/duplicates_in_pr.txt + build-and-push: + needs: flyway-conflict-check + if: success() || (needs.flyway-conflict-check.result == 'skipped') runs-on: ubuntu-latest permissions: @@ -196,42 +255,4 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} run: | - gh release edit ${{ env.new_tag }} --draft=false - - - name: Notify Discord of New Release - if: false - continue-on-error: true - shell: bash - env: - GH_TOKEN: ${{ github.token }} - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} - NEW_TAG: ${{ env.new_tag }} - run: | - set -euo pipefail - if [ -z "${DISCORD_WEBHOOK_URL:-}" ]; then - echo "DISCORD_WEBHOOK_URL not set, skipping Discord notification." - exit 0 - fi - release_json=$(gh release view "$NEW_TAG" --json name,body,url) - release_name=$(jq -r '.name' <<< "$release_json") - release_body=$(jq -r '.body' <<< "$release_json") - release_url=$(jq -r '.url' <<< "$release_json") - clean_body=$(echo "$release_body" | tr -d '\r') - max_length=1800 - if [ ${#clean_body} -gt $max_length ]; then - clean_body="${clean_body:0:$((max_length-12))}โ€ฆ [truncated]" - fi - payload=$(jq -n \ - --arg title "New Release: $release_name" \ - --arg url "$release_url" \ - --arg desc "$clean_body" \ - '{ - content: null, - embeds: [{ - title: $title, - url: $url, - description: $desc, - color: 3066993 - }] - }') - curl -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL" + gh release edit ${{ env.new_tag }} --draft=true \ No newline at end of file From 1382698d54b15389028a777ff671f19de7645c13 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 01:58:14 -0700 Subject: [PATCH 14/46] chore(deps): update dependency org.freemarker:freemarker to v2.3.34 (#1861) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- booklore-api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booklore-api/build.gradle b/booklore-api/build.gradle index db0ee10e..b56d1a9e 100644 --- a/booklore-api/build.gradle +++ b/booklore-api/build.gradle @@ -76,7 +76,7 @@ dependencies { implementation 'org.apache.commons:commons-text:1.14.0' // --- Template Engine --- - implementation 'org.freemarker:freemarker:2.3.33' + implementation 'org.freemarker:freemarker:2.3.34' // --- Test Dependencies --- testImplementation 'org.springframework.boot:spring-boot-starter-test' From eace4fcbde60fabb3e92c7dd0b2ec50f61df64e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 01:58:24 -0700 Subject: [PATCH 15/46] chore(deps): update lscr.io/linuxserver/mariadb docker tag to v11.4.8 (#1862) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- example-docker/docker-compose.yml | 2 +- example-podman/booklore-db.container | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example-docker/docker-compose.yml b/example-docker/docker-compose.yml index 9af1ce3a..6aecdc05 100644 --- a/example-docker/docker-compose.yml +++ b/example-docker/docker-compose.yml @@ -29,7 +29,7 @@ services: restart: unless-stopped mariadb: - image: lscr.io/linuxserver/mariadb:11.4.5 + image: lscr.io/linuxserver/mariadb:11.4.8 container_name: mariadb environment: - PUID=1000 diff --git a/example-podman/booklore-db.container b/example-podman/booklore-db.container index 3cc98e9e..148545bc 100644 --- a/example-podman/booklore-db.container +++ b/example-podman/booklore-db.container @@ -3,7 +3,7 @@ Description=BookLore Database Container Using MariaDB [Container] ContainerName=booklore-db -Image=lscr.io/linuxserver/mariadb:11.4.5 +Image=lscr.io/linuxserver/mariadb:11.4.8 Pod=booklore.pod Pull=always From 8a520481aea0c92cdf95b1bb0ab9356bcfa002c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 01:58:53 -0700 Subject: [PATCH 16/46] chore(deps): update dependency org.apache.commons:commons-text to v1.15.0 (#1865) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- booklore-api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booklore-api/build.gradle b/booklore-api/build.gradle index b56d1a9e..3ab2e02e 100644 --- a/booklore-api/build.gradle +++ b/booklore-api/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.14' implementation 'org.apache.commons:commons-compress:1.28.0' implementation 'org.tukaani:xz:1.11' // Required by commons-compress for 7z support - implementation 'org.apache.commons:commons-text:1.14.0' + implementation 'org.apache.commons:commons-text:1.15.0' // --- Template Engine --- implementation 'org.freemarker:freemarker:2.3.34' From 3a4a1f3000108482c4ca4546df233a26f94f8628 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 01:59:10 -0700 Subject: [PATCH 17/46] chore(deps): update dependency org.flywaydb:flyway-mysql to v11.19.0 (#1868) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- booklore-api/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booklore-api/build.gradle b/booklore-api/build.gradle index 3ab2e02e..d45283a8 100644 --- a/booklore-api/build.gradle +++ b/booklore-api/build.gradle @@ -39,7 +39,7 @@ dependencies { // --- Database & Migration --- implementation 'org.mariadb.jdbc:mariadb-java-client:3.5.6' - implementation 'org.flywaydb:flyway-mysql:11.18.0' + implementation 'org.flywaydb:flyway-mysql:11.19.0' // --- Security & Authentication --- implementation 'io.jsonwebtoken:jjwt-api:0.13.0' From 5a0daff2c40cf1051ec040260cbcdab32fadd181 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 01:59:25 -0700 Subject: [PATCH 18/46] chore(deps): update actions/checkout action to v6 (#1869) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docker-build-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 5f660dde..c5a4356a 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -77,7 +77,7 @@ jobs: steps: - name: Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 From f602137bc7932c79932f577be7182be0408d185d Mon Sep 17 00:00:00 2001 From: ACX <8075870+acx10@users.noreply.github.com> Date: Sun, 14 Dec 2025 11:35:40 -0700 Subject: [PATCH 19/46] Speed up CI builds by introducing dependency caching (#1874) Co-authored-by: acx10 --- .github/workflows/docker-build-publish.yml | 69 ++++++++++------------ Dockerfile | 18 ++++-- 2 files changed, 44 insertions(+), 43 deletions(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index c5a4356a..c8949128 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -105,13 +105,14 @@ jobs: with: java-version: '21' distribution: 'temurin' + cache: 'gradle' - name: Run Backend Tests id: backend_tests working-directory: ./booklore-api run: | echo "Running backend tests with testcontainers..." - ./gradlew test + ./gradlew test --no-daemon --parallel --build-cache continue-on-error: true - name: Publish Test Results @@ -204,42 +205,36 @@ jobs: echo "bump=$bump" >> $GITHUB_ENV echo "new_tag=$next_version" >> $GITHUB_ENV - - name: Generate Image Tag - id: set_image_tag - run: | - branch="${GITHUB_REF#refs/heads/}" - if [[ "$branch" == "master" ]]; then - image_tag="${{ env.new_tag }}" - elif [[ "$branch" == "develop" ]]; then - short_sha=$(git rev-parse --short HEAD) - image_tag="${{ env.latest_tag }}-develop-${short_sha}" - else - short_sha=$(git rev-parse --short HEAD) - image_tag="${short_sha}" - fi - echo "image_tag=$image_tag" >> $GITHUB_ENV - echo "Image tag: $image_tag" + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + booklore/booklore + ghcr.io/booklore-app/booklore + tags: | + type=sha,enable=${{ github.ref != github.event.repository.default_branch }} + type=raw,value={{branch}}-{{sha_short}},enable=${{ github.ref_name == 'develop' }} + type=raw,value=${{ env.new_tag }},enable=${{ github.ref_name == 'master' }} + type=raw,value=latest,enable=${{ github.ref_name == 'master' }} - - name: Build and Push Docker Image - run: | - docker buildx create --use - docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --build-arg APP_VERSION=${{ env.image_tag }} \ - --build-arg APP_REVISION=${{ github.sha }} \ - --tag booklore/booklore:${{ env.image_tag }} \ - --tag ghcr.io/booklore-app/booklore:${{ env.image_tag }} \ - --push . - - - name: Push Latest Tag (Only for Master) - if: github.ref == 'refs/heads/master' - run: | - docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --build-arg APP_VERSION=${{ env.new_tag }} \ - --tag booklore/booklore:latest \ - --tag ghcr.io/booklore-app/booklore:latest \ - --push . + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + APP_VERSION=${{ steps.meta.outputs.version }} + APP_REVISION=${{ github.sha }} + cache-from: | + type=gha + type=registry,ref=ghcr.io/booklore-app/booklore:buildcache + cache-to: | + type=gha,mode=max + type=registry,ref=ghcr.io/booklore-app/booklore:buildcache,mode=max - name: Update Release Draft (Only for Master) if: github.ref == 'refs/heads/master' @@ -255,4 +250,4 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} run: | - gh release edit ${{ env.new_tag }} --draft=true \ No newline at end of file + gh release edit ${{ env.new_tag }} --draft=true diff --git a/Dockerfile b/Dockerfile index dfa920da..79c8c7bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,11 +4,10 @@ FROM node:22-alpine AS angular-build WORKDIR /angular-app COPY ./booklore-ui/package.json ./booklore-ui/package-lock.json ./ -RUN npm config set registry http://registry.npmjs.org/ \ - && npm config set fetch-retries 5 \ - && npm config set fetch-retry-mintimeout 20000 \ - && npm config set fetch-retry-maxtimeout 120000 \ - && npm install --force +RUN --mount=type=cache,target=/root/.npm \ + npm config set registry http://registry.npmjs.org/ \ + && npm ci --force + COPY ./booklore-ui /angular-app/ RUN npm run build --configuration=production @@ -18,7 +17,13 @@ FROM gradle:8.14.3-jdk21-alpine AS springboot-build WORKDIR /springboot-app +# Copy only build files first to cache dependencies COPY ./booklore-api/build.gradle ./booklore-api/settings.gradle /springboot-app/ + +# Download dependencies (cached layer) +RUN --mount=type=cache,target=/home/gradle/.gradle \ + gradle dependencies --no-daemon + COPY ./booklore-api/src /springboot-app/src # Inject version into application.yaml using yq @@ -26,7 +31,8 @@ ARG APP_VERSION RUN apk add --no-cache yq && \ yq eval '.app.version = strenv(APP_VERSION)' -i /springboot-app/src/main/resources/application.yaml -RUN gradle clean build -x test +RUN --mount=type=cache,target=/home/gradle/.gradle \ + gradle clean build -x test --no-daemon --parallel # Stage 3: Final image FROM eclipse-temurin:21.0.9_10-jre-alpine From 3f14376a3a66264fa23ad8a1ed641d3ca926ad47 Mon Sep 17 00:00:00 2001 From: ACX <8075870+acx10@users.noreply.github.com> Date: Sun, 14 Dec 2025 12:16:16 -0700 Subject: [PATCH 20/46] Add Flyway migration conflict detection in CI (#1888) * Add Flyway migration conflict detection in CI * Update flyway version --------- Co-authored-by: acx10 --- .github/workflows/docker-build-publish.yml | 120 ++++++++++++--------- 1 file changed, 72 insertions(+), 48 deletions(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index c8949128..f5530c59 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -10,62 +10,86 @@ on: - '**' jobs: - flyway-conflict-check: + check-for-migrations: + name: Check for DB Migrations + if: github.event_name == 'pull_request' && ((github.base_ref == 'master' && github.head_ref == 'develop') || github.base_ref == 'develop') runs-on: ubuntu-latest + outputs: + has_migrations: ${{ steps.check_migrations.outputs.has_migrations }} steps: - - name: Checkout PR branch + - name: Checkout Repository for Diff uses: actions/checkout@v6 with: fetch-depth: 0 - - name: Determine comparison reference - id: compare_ref - run: | - bash .github/scripts/determine-compare-ref.sh "${{ github.base_ref }}" - - - name: Check for migration changes + - name: Detect Flyway Migration Changes id: check_migrations - if: steps.compare_ref.outputs.has_ref == 'true' run: | - # Check for changes in the migration directory - if git diff --name-only --diff-filter=AMRCD ${{ env.COMPARE_REF }}...HEAD | grep -q "booklore-api/src/main/resources/db/migration/"; then - echo "Migration file changes detected." + # Compare PR head with the target base branch + if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q "booklore-api/src/main/resources/db/migration/V.*.sql"; then + echo "Migration file changes detected. Proceeding with migration preview." echo "has_migrations=true" >> $GITHUB_OUTPUT else - echo "No migration file changes detected. Skipping conflict check." + echo "No migration file changes detected. Skipping migration preview." echo "has_migrations=false" >> $GITHUB_OUTPUT fi - - name: Analyze migration changes - if: steps.check_migrations.outputs.has_migrations == 'true' - id: analyze_changes + flyway-migration-preview: + name: Flyway DB Migration Preview + needs: [check-for-migrations] + if: needs.check-for-migrations.outputs.has_migrations == 'true' + runs-on: ubuntu-latest + services: + mariadb: + image: mariadb:10.6 env: - COMPARE_REF: ${{ env.COMPARE_REF }} - run: | - bash .github/scripts/analyze-changes.sh + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: booklore_test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping --silent" + --health-interval=5s + --health-timeout=5s + --health-retries=10 - - name: Extract and validate versions - if: steps.check_migrations.outputs.has_migrations == 'true' - id: validate_versions - run: | - bash .github/scripts/validate-versions.sh + steps: + - name: Checkout Base Branch + uses: actions/checkout@v6 + with: + ref: '${{ github.base_ref }}' - - name: Check for version conflicts with base - if: steps.validate_versions.outputs.has_versions == 'true' - env: - COMPARE_REF: ${{ env.COMPARE_REF }} + - name: Apply Migrations from Base Branch run: | - bash .github/scripts/check-conflicts.sh + echo "Applying migrations from '${{ github.base_ref }}' branch..." + docker run --network host \ + -v ${{ github.workspace }}:/flyway/sql \ + flyway/flyway:11.19.0-alpine \ + -url=jdbc:mariadb://127.0.0.1:3306/booklore_test \ + -user=root -password=root \ + -locations=filesystem:/flyway/sql/booklore-api/src/main/resources/db/migration \ + migrate - - name: Cleanup - if: always() && steps.check_migrations.outputs.has_migrations == 'true' + - name: Checkout Pull Request Branch + uses: actions/checkout@v6 + + - name: Apply Migrations from PR Branch run: | - rm -f /tmp/all_changes.txt /tmp/pr_files.txt /tmp/base_files.txt - rm -f /tmp/versions_*.txt /tmp/duplicates_in_pr.txt + echo "Applying new migrations from PR branch..." + docker run --network host \ + -v ${{ github.workspace }}:/flyway/sql \ + flyway/flyway:11.19.0-alpine \ + -url=jdbc:mariadb://127.0.0.1:3306/booklore_test \ + -user=root -password=root \ + -locations=filesystem:/flyway/sql/booklore-api/src/main/resources/db/migration \ + migrate + + - name: Confirm Flyway Dry Run Success + run: echo "โœ… Flyway migration preview successful. Migrations can be applied cleanly." build-and-push: - needs: flyway-conflict-check - if: success() || (needs.flyway-conflict-check.result == 'skipped') + needs: [check-for-migrations, flyway-migration-preview] + if: always() && (needs.flyway-migration-preview.result == 'success' || needs.flyway-migration-preview.result == 'skipped') runs-on: ubuntu-latest permissions: @@ -81,33 +105,33 @@ jobs: with: fetch-depth: 0 - - name: Log in to Docker Hub + - name: Authenticate to Docker Hub uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Log in to GitHub Container Registry + - name: Authenticate to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - - name: Set up QEMU for multi-arch builds + - name: Set Up QEMU for Multi-Architecture Builds uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx + - name: Set Up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Set up JDK 21 + - name: Set Up JDK 21 uses: actions/setup-java@v4 with: java-version: '21' distribution: 'temurin' cache: 'gradle' - - name: Run Backend Tests + - name: Execute Backend Tests id: backend_tests working-directory: ./booklore-api run: | @@ -115,7 +139,7 @@ jobs: ./gradlew test --no-daemon --parallel --build-cache continue-on-error: true - - name: Publish Test Results + - name: Publish Backend Test Results uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: @@ -125,7 +149,7 @@ jobs: report_individual_runs: true report_suite_logs: 'any' - - name: Upload Test Reports + - name: Upload Backend Test Reports uses: actions/upload-artifact@v4 if: always() with: @@ -135,13 +159,13 @@ jobs: booklore-api/build/test-results/ retention-days: 30 - - name: Check Test Results + - name: Validate Backend Test Results if: steps.backend_tests.outcome == 'failure' run: | echo "โŒ Backend tests failed! Check the test results above." exit 1 - - name: Get Latest Master Version + - name: Retrieve Latest Master Version Tag id: get_version run: | latest_tag=$(git tag --list "v*" --sort=-v:refname | head -n 1) @@ -149,7 +173,7 @@ jobs: echo "latest_tag=$latest_tag" >> $GITHUB_ENV echo "Latest master tag: $latest_tag" - - name: Determine Version Bump (Only for Master) + - name: Determine Version Bump (Master Only) if: github.ref == 'refs/heads/master' id: determine_bump env: @@ -236,7 +260,7 @@ jobs: type=gha,mode=max type=registry,ref=ghcr.io/booklore-app/booklore:buildcache,mode=max - - name: Update Release Draft (Only for Master) + - name: Update GitHub Release Draft (Master Only) if: github.ref == 'refs/heads/master' uses: release-drafter/release-drafter@v6 with: @@ -245,7 +269,7 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} - - name: Publish Draft Release (Only for Master) + - name: Publish GitHub Draft Release (Master Only) if: github.ref == 'refs/heads/master' env: GITHUB_TOKEN: ${{ github.token }} From ed05820658fecea4a877455f4cf8614b3b9711ba Mon Sep 17 00:00:00 2001 From: Muppetteer Date: Mon, 15 Dec 2025 13:21:19 +1100 Subject: [PATCH 21/46] fix(ui): library directory picker dialog double heading (#1867) --- booklore-ui/src/app/shared/services/dialog-launcher.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/booklore-ui/src/app/shared/services/dialog-launcher.service.ts b/booklore-ui/src/app/shared/services/dialog-launcher.service.ts index 68c3aa8b..cfd517ce 100644 --- a/booklore-ui/src/app/shared/services/dialog-launcher.service.ts +++ b/booklore-ui/src/app/shared/services/dialog-launcher.service.ts @@ -64,7 +64,7 @@ export class DialogLauncherService { openDirectoryPickerDialog(): DynamicDialogRef | null { return this.openDialog(DirectoryPickerComponent, { - header: 'Select Media Directory', + showHeader: false, styleClass: 'dynamic-dialog-minimal', }); } From 0b4bccb462225d897fe257270097b1c020046a37 Mon Sep 17 00:00:00 2001 From: Muppetteer Date: Mon, 15 Dec 2025 13:22:06 +1100 Subject: [PATCH 22/46] fix(ui): Force page reload on logout (#1870) --- booklore-ui/src/app/core/security/auth-interceptor.service.ts | 3 +-- .../components/change-password/change-password.component.ts | 1 - booklore-ui/src/app/shared/service/auth.service.ts | 4 +++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/booklore-ui/src/app/core/security/auth-interceptor.service.ts b/booklore-ui/src/app/core/security/auth-interceptor.service.ts index df6f7190..d1e237d8 100644 --- a/booklore-ui/src/app/core/security/auth-interceptor.service.ts +++ b/booklore-ui/src/app/core/security/auth-interceptor.service.ts @@ -73,7 +73,6 @@ function handle401Error(authService: AuthService, request: HttpRequest, nex } function forceLogout(authService: AuthService, router: Router, message?: string): void { - authService.logout(); - router.navigate(['/login']); if (message) console.warn(message); + authService.logout(); } diff --git a/booklore-ui/src/app/shared/components/change-password/change-password.component.ts b/booklore-ui/src/app/shared/components/change-password/change-password.component.ts index bacfadd9..6b8d8faa 100644 --- a/booklore-ui/src/app/shared/components/change-password/change-password.component.ts +++ b/booklore-ui/src/app/shared/components/change-password/change-password.component.ts @@ -73,6 +73,5 @@ export class ChangePasswordComponent { logout() { this.authService.logout(); - window.location.href = '/login'; } } diff --git a/booklore-ui/src/app/shared/service/auth.service.ts b/booklore-ui/src/app/shared/service/auth.service.ts index 68315d1d..826a53ea 100644 --- a/booklore-ui/src/app/shared/service/auth.service.ts +++ b/booklore-ui/src/app/shared/service/auth.service.ts @@ -94,7 +94,9 @@ export class AuthService { this.oAuthStorage.removeItem("id_token"); this.tokenSubject.next(null); this.getRxStompService().deactivate(); - this.router.navigate(['/login']); + + // Force reload so user's data is not cached for next user that logs in + window.location.href = '/login'; } getRxStompService(): RxStompService { From 8318dcde8e4723df7db0bab6dca1534c817a6b6d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:26:15 -0700 Subject: [PATCH 23/46] chore(deps): update actions/upload-artifact action to v6 (#1872) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docker-build-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index f5530c59..e6698141 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -150,7 +150,7 @@ jobs: report_suite_logs: 'any' - name: Upload Backend Test Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: always() with: name: test-reports From 83f5e3a31ded8cc45fa0b6a61c0fd641691ba7db Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Dec 2025 19:26:36 -0700 Subject: [PATCH 24/46] chore(deps): update actions/setup-java action to v5 (#1871) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/docker-build-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index e6698141..667bde86 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -125,7 +125,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Set Up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '21' distribution: 'temurin' From a4a94b731a7b4af07e998abf7060b542c8996a98 Mon Sep 17 00:00:00 2001 From: WorldTeacher <41587052+WorldTeacher@users.noreply.github.com> Date: Tue, 16 Dec 2025 03:24:01 +0100 Subject: [PATCH 25/46] feat(opds): allow user to set sorting for opds feed in settings (#1824) * feat(opds): allow user to set sorting for opds feed in settings * patch(opds): re-add search normalization * patch(opds): add series to feedid determination --------- Co-authored-by: WorldTeacher --- .../controller/OpdsUserV2Controller.java | 12 +- .../booklore/model/dto/OpdsUserV2.java | 2 + .../dto/request/OpdsUserV2CreateRequest.java | 2 + .../dto/request/OpdsUserV2UpdateRequest.java | 10 ++ .../model/entity/OpdsUserV2Entity.java | 6 + .../booklore/model/enums/OpdsSortOrder.java | 13 ++ .../service/opds/OpdsBookService.java | 169 +++++++++++++++++- .../service/opds/OpdsFeedService.java | 16 ++ .../service/opds/OpdsUserV2Service.java | 15 ++ .../V99__Add_sort_order_to_opds_user_v2.sql | 2 + .../booklore/model/enums/OpdsSortOrder.java | 13 ++ .../service/opds/OpdsFeedServiceTest.java | 6 + .../settings/opds-settings/opds-settings.html | 66 ++++++- .../settings/opds-settings/opds-settings.scss | 27 +++ .../settings/opds-settings/opds-settings.ts | 63 ++++++- .../settings/opds-settings/opds.service.ts | 12 ++ 16 files changed, 427 insertions(+), 7 deletions(-) create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/OpdsUserV2UpdateRequest.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/enums/OpdsSortOrder.java create mode 100644 booklore-api/src/main/resources/db/migration/V99__Add_sort_order_to_opds_user_v2.sql create mode 100644 booklore-api/src/test/java/com/adityachandel/booklore/model/enums/OpdsSortOrder.java diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/OpdsUserV2Controller.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/OpdsUserV2Controller.java index 3224db80..763ce9ce 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/controller/OpdsUserV2Controller.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/OpdsUserV2Controller.java @@ -2,6 +2,7 @@ package com.adityachandel.booklore.controller; import com.adityachandel.booklore.model.dto.OpdsUserV2; import com.adityachandel.booklore.model.dto.request.OpdsUserV2CreateRequest; +import com.adityachandel.booklore.model.dto.request.OpdsUserV2UpdateRequest; import com.adityachandel.booklore.service.opds.OpdsUserV2Service; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -46,4 +47,13 @@ public class OpdsUserV2Controller { @Parameter(description = "ID of the OPDS user to delete") @PathVariable Long id) { service.deleteOpdsUser(id); } -} + + @Operation(summary = "Update OPDS user", description = "Update an OPDS user's settings by ID.") + @ApiResponse(responseCode = "200", description = "OPDS user updated successfully") + @PatchMapping("/{id}") + @PreAuthorize("@securityUtil.isAdmin() or @securityUtil.canAccessOpds()") + public OpdsUserV2 updateUser( + @Parameter(description = "ID of the OPDS user to update") @PathVariable Long id, + @Parameter(description = "OPDS user update request") @RequestBody OpdsUserV2UpdateRequest updateRequest) { + return service.updateOpdsUser(id, updateRequest); + }} \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/OpdsUserV2.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/OpdsUserV2.java index 11e78ae5..3f39b710 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/OpdsUserV2.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/OpdsUserV2.java @@ -1,5 +1,6 @@ package com.adityachandel.booklore.model.dto; +import com.adityachandel.booklore.model.enums.OpdsSortOrder; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; @@ -14,4 +15,5 @@ public class OpdsUserV2 { private String username; @JsonIgnore private String passwordHash; + private OpdsSortOrder sortOrder; } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/OpdsUserV2CreateRequest.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/OpdsUserV2CreateRequest.java index 88bb8664..eb087871 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/OpdsUserV2CreateRequest.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/OpdsUserV2CreateRequest.java @@ -1,9 +1,11 @@ package com.adityachandel.booklore.model.dto.request; +import com.adityachandel.booklore.model.enums.OpdsSortOrder; import lombok.Data; @Data public class OpdsUserV2CreateRequest { private String username; private String password; + private OpdsSortOrder sortOrder; } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/OpdsUserV2UpdateRequest.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/OpdsUserV2UpdateRequest.java new file mode 100644 index 00000000..14b3b8fa --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/OpdsUserV2UpdateRequest.java @@ -0,0 +1,10 @@ +package com.adityachandel.booklore.model.dto.request; + +import com.adityachandel.booklore.model.enums.OpdsSortOrder; +import jakarta.validation.constraints.NotNull; + +public record OpdsUserV2UpdateRequest( + @NotNull(message = "Sort order is required") + OpdsSortOrder sortOrder +) { +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/entity/OpdsUserV2Entity.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/entity/OpdsUserV2Entity.java index 82895eb4..118364bb 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/model/entity/OpdsUserV2Entity.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/entity/OpdsUserV2Entity.java @@ -1,5 +1,6 @@ package com.adityachandel.booklore.model.entity; +import com.adityachandel.booklore.model.enums.OpdsSortOrder; import jakarta.persistence.*; import lombok.*; @@ -28,6 +29,11 @@ public class OpdsUserV2Entity { @Column(name = "password_hash", nullable = false) private String passwordHash; + @Enumerated(EnumType.STRING) + @Column(name = "sort_order", length = 20) + @Builder.Default + private OpdsSortOrder sortOrder = OpdsSortOrder.RECENT; + @Column(name = "created_at", nullable = false, updatable = false) private Instant createdAt; diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/OpdsSortOrder.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/OpdsSortOrder.java new file mode 100644 index 00000000..6f5aa231 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/enums/OpdsSortOrder.java @@ -0,0 +1,13 @@ +package com.adityachandel.booklore.model.enums; + +public enum OpdsSortOrder { + RECENT, + TITLE_ASC, + TITLE_DESC, + AUTHOR_ASC, + AUTHOR_DESC, + SERIES_ASC, + SERIES_DESC, + RATING_ASC, + RATING_DESC +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsBookService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsBookService.java index ea39f70c..82cc14e8 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsBookService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsBookService.java @@ -7,11 +7,12 @@ import com.adityachandel.booklore.model.dto.*; import com.adityachandel.booklore.model.entity.BookEntity; import com.adityachandel.booklore.model.entity.BookLoreUserEntity; import com.adityachandel.booklore.model.entity.ShelfEntity; +import com.adityachandel.booklore.model.enums.OpdsSortOrder; import com.adityachandel.booklore.repository.BookOpdsRepository; import com.adityachandel.booklore.repository.ShelfRepository; import com.adityachandel.booklore.repository.UserRepository; -import com.adityachandel.booklore.service.library.LibraryService; import com.adityachandel.booklore.util.BookUtils; +import com.adityachandel.booklore.service.library.LibraryService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -400,4 +401,170 @@ public class OpdsBookService { } return dto; } + + public Page applySortOrder(Page booksPage, OpdsSortOrder sortOrder) { + if (sortOrder == null || sortOrder == OpdsSortOrder.RECENT) { + return booksPage; // Already sorted by addedOn DESC from repository + } + + List sortedBooks = new ArrayList<>(booksPage.getContent()); + + switch (sortOrder) { + case TITLE_ASC -> sortedBooks.sort((b1, b2) -> { + String title1 = b1.getMetadata() != null && b1.getMetadata().getTitle() != null + ? b1.getMetadata().getTitle() : ""; + String title2 = b2.getMetadata() != null && b2.getMetadata().getTitle() != null + ? b2.getMetadata().getTitle() : ""; + return title1.compareToIgnoreCase(title2); + }); + case TITLE_DESC -> sortedBooks.sort((b1, b2) -> { + String title1 = b1.getMetadata() != null && b1.getMetadata().getTitle() != null + ? b1.getMetadata().getTitle() : ""; + String title2 = b2.getMetadata() != null && b2.getMetadata().getTitle() != null + ? b2.getMetadata().getTitle() : ""; + return title2.compareToIgnoreCase(title1); + }); + case AUTHOR_ASC -> sortedBooks.sort((b1, b2) -> { + String author1 = getFirstAuthor(b1); + String author2 = getFirstAuthor(b2); + return author1.compareToIgnoreCase(author2); + }); + case AUTHOR_DESC -> sortedBooks.sort((b1, b2) -> { + String author1 = getFirstAuthor(b1); + String author2 = getFirstAuthor(b2); + return author2.compareToIgnoreCase(author1); + }); + case SERIES_ASC -> sortedBooks.sort((b1, b2) -> { + String series1 = getSeriesName(b1); + String series2 = getSeriesName(b2); + boolean hasSeries1 = !series1.isEmpty(); + boolean hasSeries2 = !series2.isEmpty(); + + // Books without series come after books with series + if (!hasSeries1 && !hasSeries2) { + // Both have no series, sort by addedOn descending + return compareByAddedOn(b2, b1); + } + if (!hasSeries1) return 1; + if (!hasSeries2) return -1; + + // Both have series, sort by series name then number + int seriesComp = series1.compareToIgnoreCase(series2); + if (seriesComp != 0) return seriesComp; + return Float.compare(getSeriesNumber(b1), getSeriesNumber(b2)); + }); + case SERIES_DESC -> sortedBooks.sort((b1, b2) -> { + String series1 = getSeriesName(b1); + String series2 = getSeriesName(b2); + boolean hasSeries1 = !series1.isEmpty(); + boolean hasSeries2 = !series2.isEmpty(); + + // Books without series come after books with series + if (!hasSeries1 && !hasSeries2) { + // Both have no series, sort by addedOn descending + return compareByAddedOn(b2, b1); + } + if (!hasSeries1) return 1; + if (!hasSeries2) return -1; + + // Both have series, sort by series name then number + int seriesComp = series2.compareToIgnoreCase(series1); + if (seriesComp != 0) return seriesComp; + return Float.compare(getSeriesNumber(b2), getSeriesNumber(b1)); + }); + case RATING_ASC -> sortedBooks.sort((b1, b2) -> { + Float rating1 = calculateRating(b1); + Float rating2 = calculateRating(b2); + // Books with no rating go to the end + if (rating1 == null && rating2 == null) { + // Both have no rating, fall back to addedOn descending + return compareByAddedOn(b2, b1); + } + if (rating1 == null) return 1; + if (rating2 == null) return -1; + int ratingComp = Float.compare(rating1, rating2); // Ascending order (lowest first) + if (ratingComp != 0) return ratingComp; + // Same rating, fall back to addedOn descending + return compareByAddedOn(b2, b1); + }); + case RATING_DESC -> sortedBooks.sort((b1, b2) -> { + Float rating1 = calculateRating(b1); + Float rating2 = calculateRating(b2); + // Books with no rating go to the end + if (rating1 == null && rating2 == null) { + // Both have no rating, fall back to addedOn descending + return compareByAddedOn(b2, b1); + } + if (rating1 == null) return 1; + if (rating2 == null) return -1; + int ratingComp = Float.compare(rating2, rating1); // Descending order (highest first) + if (ratingComp != 0) return ratingComp; + // Same rating, fall back to addedOn descending + return compareByAddedOn(b2, b1); + }); + } + + return new PageImpl<>(sortedBooks, booksPage.getPageable(), booksPage.getTotalElements()); + } + + private String getFirstAuthor(Book book) { + if (book.getMetadata() != null && book.getMetadata().getAuthors() != null + && !book.getMetadata().getAuthors().isEmpty()) { + return book.getMetadata().getAuthors().iterator().next(); + } + return ""; + } + + private String getSeriesName(Book book) { + if (book.getMetadata() != null && book.getMetadata().getSeriesName() != null) { + return book.getMetadata().getSeriesName(); + } + return ""; + } + + private Float getSeriesNumber(Book book) { + if (book.getMetadata() != null && book.getMetadata().getSeriesNumber() != null) { + return book.getMetadata().getSeriesNumber(); + } + return Float.MAX_VALUE; + } + + private int compareByAddedOn(Book b1, Book b2) { + if (b1.getAddedOn() == null && b2.getAddedOn() == null) return 0; + if (b1.getAddedOn() == null) return 1; + if (b2.getAddedOn() == null) return -1; + return b1.getAddedOn().compareTo(b2.getAddedOn()); + } + + private Float calculateRating(Book book) { + if (book.getMetadata() == null) { + return null; + } + + Double hardcoverRating = book.getMetadata().getHardcoverRating(); + Double amazonRating = book.getMetadata().getAmazonRating(); + Double goodreadsRating = book.getMetadata().getGoodreadsRating(); + + double sum = 0; + int count = 0; + + if (hardcoverRating != null && hardcoverRating > 0) { + sum += hardcoverRating; + count++; + } + if (amazonRating != null && amazonRating > 0) { + sum += amazonRating; + count++; + } + if (goodreadsRating != null && goodreadsRating > 0) { + sum += goodreadsRating; + count++; + } + + if (count == 0) { + return null; + } + + return (float) (sum / count); + } } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsFeedService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsFeedService.java index 3372cbce..18e80ea9 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsFeedService.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsFeedService.java @@ -5,6 +5,7 @@ import com.adityachandel.booklore.config.security.userdetails.OpdsUserDetails; import com.adityachandel.booklore.model.dto.Book; import com.adityachandel.booklore.model.dto.Library; import com.adityachandel.booklore.model.dto.MagicShelf; +import com.adityachandel.booklore.model.enums.OpdsSortOrder; import com.adityachandel.booklore.service.MagicShelfService; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -328,6 +329,7 @@ public class OpdsFeedService { int size = Math.min(parseLongParam(request, "size", (long) DEFAULT_PAGE_SIZE).intValue(), MAX_PAGE_SIZE); Long userId = getUserId(); + OpdsSortOrder sortOrder = getSortOrder(); Page booksPage; if (magicShelfId != null) { @@ -340,6 +342,9 @@ public class OpdsFeedService { booksPage = opdsBookService.getBooksPage(userId, query, libraryId, shelfId, page - 1, size); } + // Apply user's preferred sort order + booksPage = opdsBookService.applySortOrder(booksPage, sortOrder); + String feedTitle = determineFeedTitle(libraryId, shelfId, magicShelfId, author, series); String feedId = determineFeedId(libraryId, shelfId, magicShelfId, author, series); @@ -375,10 +380,14 @@ public class OpdsFeedService { public String generateRecentFeed(HttpServletRequest request) { Long userId = getUserId(); + OpdsSortOrder sortOrder = getSortOrder(); int page = Math.max(1, parseLongParam(request, "page", 1L).intValue()); int size = Math.min(parseLongParam(request, "size", (long) DEFAULT_PAGE_SIZE).intValue(), MAX_PAGE_SIZE); Page booksPage = opdsBookService.getRecentBooksPage(userId, page - 1, size); + + // Apply user's preferred sort order + booksPage = opdsBookService.applySortOrder(booksPage, sortOrder); var feed = new StringBuilder(""" @@ -630,4 +639,11 @@ public class OpdsFeedService { ? details.getOpdsUserV2().getUserId() : null; } + + private OpdsSortOrder getSortOrder() { + OpdsUserDetails details = authenticationService.getOpdsUser(); + return details != null && details.getOpdsUserV2() != null && details.getOpdsUserV2().getSortOrder() != null + ? details.getOpdsUserV2().getSortOrder() + : OpdsSortOrder.RECENT; + } } diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsUserV2Service.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsUserV2Service.java index 222f88ea..063ad555 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsUserV2Service.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/opds/OpdsUserV2Service.java @@ -5,6 +5,7 @@ import com.adityachandel.booklore.mapper.OpdsUserV2Mapper; import com.adityachandel.booklore.model.dto.BookLoreUser; import com.adityachandel.booklore.model.dto.OpdsUserV2; import com.adityachandel.booklore.model.dto.request.OpdsUserV2CreateRequest; +import com.adityachandel.booklore.model.dto.request.OpdsUserV2UpdateRequest; import com.adityachandel.booklore.model.entity.BookLoreUserEntity; import com.adityachandel.booklore.model.entity.OpdsUserV2Entity; import com.adityachandel.booklore.repository.OpdsUserV2Repository; @@ -45,6 +46,7 @@ public class OpdsUserV2Service { .user(userEntity) .username(request.getUsername()) .passwordHash(passwordEncoder.encode(request.getPassword())) + .sortOrder(request.getSortOrder() != null ? request.getSortOrder() : com.adityachandel.booklore.model.enums.OpdsSortOrder.RECENT) .build(); return mapper.toDto(opdsUserV2Repository.save(opdsUserV2)); @@ -64,4 +66,17 @@ public class OpdsUserV2Service { } opdsUserV2Repository.delete(user); } + + public OpdsUserV2 updateOpdsUser(Long userId, OpdsUserV2UpdateRequest request) { + BookLoreUser bookLoreUser = authenticationService.getAuthenticatedUser(); + OpdsUserV2Entity user = opdsUserV2Repository.findById(userId) + .orElseThrow(() -> new RuntimeException("User not found with ID: " + userId)); + + if (!user.getUser().getId().equals(bookLoreUser.getId())) { + throw new AccessDeniedException("You are not allowed to update this user"); + } + + user.setSortOrder(request.sortOrder()); + return mapper.toDto(opdsUserV2Repository.save(user)); + } } \ No newline at end of file diff --git a/booklore-api/src/main/resources/db/migration/V99__Add_sort_order_to_opds_user_v2.sql b/booklore-api/src/main/resources/db/migration/V99__Add_sort_order_to_opds_user_v2.sql new file mode 100644 index 00000000..e981c7f9 --- /dev/null +++ b/booklore-api/src/main/resources/db/migration/V99__Add_sort_order_to_opds_user_v2.sql @@ -0,0 +1,2 @@ +-- Add sort_order column to opds_user_v2 table +ALTER TABLE opds_user_v2 ADD COLUMN sort_order VARCHAR(20) NOT NULL DEFAULT 'RECENT'; diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/model/enums/OpdsSortOrder.java b/booklore-api/src/test/java/com/adityachandel/booklore/model/enums/OpdsSortOrder.java new file mode 100644 index 00000000..6f5aa231 --- /dev/null +++ b/booklore-api/src/test/java/com/adityachandel/booklore/model/enums/OpdsSortOrder.java @@ -0,0 +1,13 @@ +package com.adityachandel.booklore.model.enums; + +public enum OpdsSortOrder { + RECENT, + TITLE_ASC, + TITLE_DESC, + AUTHOR_ASC, + AUTHOR_DESC, + SERIES_ASC, + SERIES_DESC, + RATING_ASC, + RATING_DESC +} diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/service/opds/OpdsFeedServiceTest.java b/booklore-api/src/test/java/com/adityachandel/booklore/service/opds/OpdsFeedServiceTest.java index 84715638..b3675d77 100644 --- a/booklore-api/src/test/java/com/adityachandel/booklore/service/opds/OpdsFeedServiceTest.java +++ b/booklore-api/src/test/java/com/adityachandel/booklore/service/opds/OpdsFeedServiceTest.java @@ -8,6 +8,7 @@ import com.adityachandel.booklore.model.dto.Library; import com.adityachandel.booklore.model.dto.OpdsUserV2; import com.adityachandel.booklore.model.entity.ShelfEntity; import com.adityachandel.booklore.model.enums.BookFileType; +import com.adityachandel.booklore.model.enums.OpdsSortOrder; import com.adityachandel.booklore.service.MagicShelfService; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.BeforeEach; @@ -51,6 +52,7 @@ class OpdsFeedServiceTest { OpdsUserV2 v2 = mock(OpdsUserV2.class); when(userDetails.getOpdsUserV2()).thenReturn(v2); when(v2.getUserId()).thenReturn(TEST_USER_ID); + when(v2.getSortOrder()).thenReturn(OpdsSortOrder.RECENT); when(authenticationService.getOpdsUser()).thenReturn(userDetails); return userDetails; } @@ -152,6 +154,7 @@ class OpdsFeedServiceTest { Page page = new PageImpl<>(List.of(book), PageRequest.of(0, 50), 1); when(opdsBookService.getBooksPage(eq(TEST_USER_ID), any(), any(), any(), eq(0), eq(50))).thenReturn(page); + when(opdsBookService.applySortOrder(any(), any())).thenReturn(page); String xml = opdsFeedService.generateCatalogFeed(request); assertThat(xml).contains("Book Title"); @@ -173,6 +176,7 @@ class OpdsFeedServiceTest { Page page = new PageImpl<>(Collections.emptyList(), PageRequest.of(0, 50), 0); when(opdsBookService.getBooksPage(any(), any(), any(), any(), anyInt(), anyInt())).thenReturn(page); + when(opdsBookService.applySortOrder(any(), any())).thenReturn(page); String xml = opdsFeedService.generateCatalogFeed(request); assertThat(xml).contains(""); @@ -196,6 +200,7 @@ class OpdsFeedServiceTest { Page page = new PageImpl<>(List.of(book), PageRequest.of(0, 50), 1); when(opdsBookService.getRecentBooksPage(eq(TEST_USER_ID), eq(0), eq(50))).thenReturn(page); + when(opdsBookService.applySortOrder(any(), any())).thenReturn(page); String xml = opdsFeedService.generateRecentFeed(request); assertThat(xml).contains("Recent Book"); @@ -214,6 +219,7 @@ class OpdsFeedServiceTest { Page page = new PageImpl<>(Collections.emptyList(), PageRequest.of(0, 50), 0); when(opdsBookService.getRecentBooksPage(any(), anyInt(), anyInt())).thenReturn(page); + when(opdsBookService.applySortOrder(any(), any())).thenReturn(page); String xml = opdsFeedService.generateRecentFeed(request); assertThat(xml).contains(""); diff --git a/booklore-ui/src/app/features/settings/opds-settings/opds-settings.html b/booklore-ui/src/app/features/settings/opds-settings/opds-settings.html index 02426729..870ccf0e 100644 --- a/booklore-ui/src/app/features/settings/opds-settings/opds-settings.html +++ b/booklore-ui/src/app/features/settings/opds-settings/opds-settings.html @@ -113,6 +113,12 @@ Username
+ +
+ + Sort Order +
+
@@ -137,6 +143,47 @@ {{ user.username }}
+ + @if (editingUserId === user.id) { +
+ + + + + + +
+ } @else { +
+ {{ getSortOrderLabel(user.sortOrder) }} + + +
+ } +
- +

No users found

@@ -211,6 +258,23 @@ [(ngModel)]="newUser.password" placeholder="Enter password"/>
+
+ + + + + + This will determine how books appear in the OPDS feed for this user + +
diff --git a/booklore-ui/src/app/features/settings/opds-settings/opds-settings.scss b/booklore-ui/src/app/features/settings/opds-settings/opds-settings.scss index fb02f36d..2c19000f 100644 --- a/booklore-ui/src/app/features/settings/opds-settings/opds-settings.scss +++ b/booklore-ui/src/app/features/settings/opds-settings/opds-settings.scss @@ -264,6 +264,17 @@ font-weight: 500; } +.sort-order-badge { + display: inline-flex; + align-items: center; + padding: 0.375rem 0.75rem; + border-radius: 1rem; + background: var(--p-primary-50); + color: var(--p-primary-700); + font-size: 0.8125rem; + font-weight: 500; +} + .actions-cell { text-align: center; } @@ -332,6 +343,22 @@ color: var(--p-text-muted-color); } } + + .form-hint { + display: flex; + align-items: flex-start; + gap: 0.375rem; + color: var(--p-text-muted-color); + font-size: 0.75rem; + line-height: 1.4; + margin-top: 0.25rem; + + .pi { + font-size: 0.625rem; + margin-top: 0.125rem; + flex-shrink: 0; + } + } } .dialog-actions { diff --git a/booklore-ui/src/app/features/settings/opds-settings/opds-settings.ts b/booklore-ui/src/app/features/settings/opds-settings/opds-settings.ts index 5a20921d..bcd09557 100644 --- a/booklore-ui/src/app/features/settings/opds-settings/opds-settings.ts +++ b/booklore-ui/src/app/features/settings/opds-settings/opds-settings.ts @@ -9,7 +9,7 @@ import {Dialog} from 'primeng/dialog'; import {FormsModule} from '@angular/forms'; import {ConfirmDialog} from 'primeng/confirmdialog'; import {ConfirmationService, MessageService} from 'primeng/api'; -import {OpdsService, OpdsUserV2, OpdsUserV2CreateRequest} from './opds.service'; +import {OpdsService, OpdsSortOrder, OpdsUserV2, OpdsUserV2CreateRequest} from './opds.service'; import {catchError, filter, take, takeUntil, tap} from 'rxjs/operators'; import {UserService} from '../user-management/user.service'; import {of, Subject} from 'rxjs'; @@ -18,6 +18,7 @@ import {ToggleSwitch} from 'primeng/toggleswitch'; import {AppSettingsService} from '../../../shared/service/app-settings.service'; import {AppSettingKey} from '../../../shared/model/app-settings.model'; import {ExternalDocLinkComponent} from '../../../shared/components/external-doc-link/external-doc-link.component'; +import {Select} from 'primeng/select'; @Component({ selector: 'app-opds-settings', @@ -32,7 +33,8 @@ import {ExternalDocLinkComponent} from '../../../shared/components/external-doc- TableModule, Password, ToggleSwitch, - ExternalDocLinkComponent + ExternalDocLinkComponent, + Select ], providers: [ConfirmationService], templateUrl: './opds-settings.html', @@ -52,13 +54,28 @@ export class OpdsSettings implements OnInit, OnDestroy { users: OpdsUserV2[] = []; loading = false; showCreateUserDialog = false; - newUser: OpdsUserV2CreateRequest = {username: '', password: ''}; + newUser: OpdsUserV2CreateRequest = {username: '', password: '', sortOrder: 'RECENT'}; passwordVisibility: boolean[] = []; hasPermission = false; + editingUserId: number | null = null; + editingSortOrder: OpdsSortOrder | null = null; + private readonly destroy$ = new Subject(); dummyPassword: string = "***********************"; + sortOrderOptions = [ + { label: 'Recently Added', value: 'RECENT' as OpdsSortOrder }, + { label: 'Title (A-Z)', value: 'TITLE_ASC' as OpdsSortOrder }, + { label: 'Title (Z-A)', value: 'TITLE_DESC' as OpdsSortOrder }, + { label: 'Author (A-Z)', value: 'AUTHOR_ASC' as OpdsSortOrder }, + { label: 'Author (Z-A)', value: 'AUTHOR_DESC' as OpdsSortOrder }, + { label: 'Series (A-Z)', value: 'SERIES_ASC' as OpdsSortOrder }, + { label: 'Series (Z-A)', value: 'SERIES_DESC' as OpdsSortOrder }, + { label: 'Rating (Low to High)', value: 'RATING_ASC' as OpdsSortOrder }, + { label: 'Rating (High to Low)', value: 'RATING_DESC' as OpdsSortOrder } + ]; + ngOnInit(): void { this.loading = true; @@ -189,13 +206,51 @@ export class OpdsSettings implements OnInit, OnDestroy { private resetCreateUserDialog(): void { this.showCreateUserDialog = false; - this.newUser = {username: '', password: ''}; + this.newUser = {username: '', password: '', sortOrder: 'RECENT'}; } private showMessage(severity: string, summary: string, detail: string): void { this.messageService.add({severity, summary, detail}); } + getSortOrderLabel(sortOrder?: OpdsSortOrder): string { + if (!sortOrder) return 'Recently Added'; + const option = this.sortOrderOptions.find(o => o.value === sortOrder); + return option ? option.label : 'Recently Added'; + } + + startEdit(user: OpdsUserV2): void { + this.editingUserId = user.id; + this.editingSortOrder = user.sortOrder || 'RECENT'; + } + + cancelEdit(): void { + this.editingUserId = null; + this.editingSortOrder = null; + } + + saveSortOrder(user: OpdsUserV2): void { + if (!this.editingSortOrder || !user.id) return; + + this.opdsService.updateUser(user.id, this.editingSortOrder).pipe( + takeUntil(this.destroy$), + catchError(err => { + console.error('Error updating sort order:', err); + this.showMessage('error', 'Error', 'Failed to update sort order'); + return of(null); + }) + ).subscribe(updatedUser => { + if (updatedUser) { + const index = this.users.findIndex(u => u.id === user.id); + if (index !== -1) { + this.users[index] = updatedUser; + } + this.showMessage('success', 'Success', 'Sort order updated successfully'); + } + this.cancelEdit(); + }); + } + ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); diff --git a/booklore-ui/src/app/features/settings/opds-settings/opds.service.ts b/booklore-ui/src/app/features/settings/opds-settings/opds.service.ts index a45bfdcd..772cb77f 100644 --- a/booklore-ui/src/app/features/settings/opds-settings/opds.service.ts +++ b/booklore-ui/src/app/features/settings/opds-settings/opds.service.ts @@ -3,15 +3,23 @@ import {HttpClient} from '@angular/common/http'; import {Observable} from 'rxjs'; import {API_CONFIG} from '../../../core/config/api-config'; +export type OpdsSortOrder = 'RECENT' | 'TITLE_ASC' | 'TITLE_DESC' | 'AUTHOR_ASC' | 'AUTHOR_DESC' | 'SERIES_ASC' | 'SERIES_DESC' | 'RATING_ASC' | 'RATING_DESC'; + export interface OpdsUserV2CreateRequest { username: string; password: string; + sortOrder?: OpdsSortOrder; +} + +export interface OpdsUserV2UpdateRequest { + sortOrder: OpdsSortOrder; } export interface OpdsUserV2 { id: number; userId: number; username: string; + sortOrder?: OpdsSortOrder; } @Injectable({ @@ -30,6 +38,10 @@ export class OpdsService { return this.http.post(this.baseUrl, user); } + updateUser(id: number, sortOrder: OpdsSortOrder): Observable { + return this.http.patch(`${this.baseUrl}/${id}`, { sortOrder }); + } + deleteCredential(id: number): Observable { return this.http.delete(`${this.baseUrl}/${id}`); } From 7ef92ba4594e0190aa9c923188147a39b492713e Mon Sep 17 00:00:00 2001 From: acx10 Date: Mon, 15 Dec 2025 19:30:45 -0700 Subject: [PATCH 26/46] Skip docker login and image push on pull requests --- .github/workflows/docker-build-publish.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 667bde86..246de92a 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -106,12 +106,14 @@ jobs: fetch-depth: 0 - name: Authenticate to Docker Hub + if: github.event_name == 'push' uses: docker/login-action@v2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Authenticate to GitHub Container Registry + if: github.event_name == 'push' uses: docker/login-action@v2 with: registry: ghcr.io @@ -247,7 +249,7 @@ jobs: with: context: . platforms: linux/amd64,linux/arm64 - push: true + push: ${{ github.event_name == 'push' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | From 32386a405b3c8890bfda550afd985e5b3de52ec3 Mon Sep 17 00:00:00 2001 From: acx10 Date: Mon, 15 Dec 2025 19:57:14 -0700 Subject: [PATCH 27/46] Fix flyway file version --- ...o_opds_user_v2.sql => V73__Add_sort_order_to_opds_user_v2.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename booklore-api/src/main/resources/db/migration/{V99__Add_sort_order_to_opds_user_v2.sql => V73__Add_sort_order_to_opds_user_v2.sql} (100%) diff --git a/booklore-api/src/main/resources/db/migration/V99__Add_sort_order_to_opds_user_v2.sql b/booklore-api/src/main/resources/db/migration/V73__Add_sort_order_to_opds_user_v2.sql similarity index 100% rename from booklore-api/src/main/resources/db/migration/V99__Add_sort_order_to_opds_user_v2.sql rename to booklore-api/src/main/resources/db/migration/V73__Add_sort_order_to_opds_user_v2.sql From 5a54ed509d59d5deba60d343c5937bfaa6798957 Mon Sep 17 00:00:00 2001 From: acx10 Date: Mon, 15 Dec 2025 21:20:39 -0700 Subject: [PATCH 28/46] UI Tweaks --- .../book-browser/BookDialogHelperService.ts | 11 ++++++----- .../multi-book-metadata-editor-component.html | 2 +- .../app/shared/services/dialog-launcher.service.ts | 8 +++++--- booklore-ui/src/assets/layout/styles/global.scss | 12 ++++++++++++ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/booklore-ui/src/app/features/book/components/book-browser/BookDialogHelperService.ts b/booklore-ui/src/app/features/book/components/book-browser/BookDialogHelperService.ts index 8ad16f61..07b84b3c 100644 --- a/booklore-ui/src/app/features/book/components/book-browser/BookDialogHelperService.ts +++ b/booklore-ui/src/app/features/book/components/book-browser/BookDialogHelperService.ts @@ -20,7 +20,7 @@ import {AdditionalFileUploaderComponent} from '../additional-file-uploader/addit export class BookDialogHelperService { private dialogLauncherService = inject(DialogLauncherService); - + private openDialog(component: any, options: {}): DynamicDialogRef | null { return this.dialogLauncherService.openDialog(component, options); } @@ -36,7 +36,7 @@ export class BookDialogHelperService { } openShelfAssignerDialog(book: Book | null, bookIds: Set | null): DynamicDialogRef | null { - const data:any = {}; + const data: any = {}; if (book !== null) { data.isMultiBooks = false; data.book = book; @@ -87,7 +87,7 @@ export class BookDialogHelperService { data: { bookIds: Array.from(bookIds), }, - styleClass: 'dialog-maximal', + styleClass: 'dialog-maximal' }); } @@ -97,7 +97,7 @@ export class BookDialogHelperService { data: { bookIds: Array.from(bookIds), }, - styleClass: 'dialog-maximal', + styleClass: 'dialog-full' }); } @@ -108,7 +108,8 @@ export class BookDialogHelperService { data: { bookIds: Array.from(bookIds), }, - styleClass: 'dialog-maximal', + styleClass: 'dialog-full', + maximizable: true, }); } diff --git a/booklore-ui/src/app/features/metadata/component/multi-book-metadata-editor/multi-book-metadata-editor-component.html b/booklore-ui/src/app/features/metadata/component/multi-book-metadata-editor/multi-book-metadata-editor-component.html index d77168d9..881c8df1 100644 --- a/booklore-ui/src/app/features/metadata/component/multi-book-metadata-editor/multi-book-metadata-editor-component.html +++ b/booklore-ui/src/app/features/metadata/component/multi-book-metadata-editor/multi-book-metadata-editor-component.html @@ -8,7 +8,7 @@ Search Metadata } - + @if (admin || canEditMetadata) { Date: Mon, 15 Dec 2025 21:37:40 -0700 Subject: [PATCH 29/46] Fix tagging --- .github/workflows/docker-build-publish.yml | 52 +++++++++++++++------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 246de92a..c83b698c 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -231,29 +231,34 @@ jobs: echo "bump=$bump" >> $GITHUB_ENV echo "new_tag=$next_version" >> $GITHUB_ENV - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: | - booklore/booklore - ghcr.io/booklore-app/booklore - tags: | - type=sha,enable=${{ github.ref != github.event.repository.default_branch }} - type=raw,value={{branch}}-{{sha_short}},enable=${{ github.ref_name == 'develop' }} - type=raw,value=${{ env.new_tag }},enable=${{ github.ref_name == 'master' }} - type=raw,value=latest,enable=${{ github.ref_name == 'master' }} + - name: Generate Image Tag + id: set_image_tag + run: | + branch="${GITHUB_REF#refs/heads/}" + if [[ "$branch" == "master" ]]; then + image_tag="${{ env.new_tag }}" + elif [[ "$branch" == "develop" ]]; then + short_sha=$(git rev-parse --short HEAD) + image_tag="${{ env.latest_tag }}-develop-${short_sha}" + else + short_sha=$(git rev-parse --short HEAD) + image_tag="${short_sha}" + fi + echo "image_tag=$image_tag" >> $GITHUB_ENV + echo "Image tag: $image_tag" - name: Build and push Docker image + if: github.event_name == 'push' uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 - push: ${{ github.event_name == 'push' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + push: true + tags: | + booklore/booklore:${{ env.image_tag }} + ghcr.io/booklore-app/booklore:${{ env.image_tag }} build-args: | - APP_VERSION=${{ steps.meta.outputs.version }} + APP_VERSION=${{ env.image_tag }} APP_REVISION=${{ github.sha }} cache-from: | type=gha @@ -262,6 +267,21 @@ jobs: type=gha,mode=max type=registry,ref=ghcr.io/booklore-app/booklore:buildcache,mode=max + - name: Push Latest Tag (Master Only) + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: | + booklore/booklore:latest + ghcr.io/booklore-app/booklore:latest + build-args: | + APP_VERSION=${{ env.new_tag }} + APP_REVISION=${{ github.sha }} + cache-from: type=gha + - name: Update GitHub Release Draft (Master Only) if: github.ref == 'refs/heads/master' uses: release-drafter/release-drafter@v6 From 150038d308eddb301d6b6dbdf1696f8da49eac53 Mon Sep 17 00:00:00 2001 From: acx10 Date: Mon, 15 Dec 2025 21:57:14 -0700 Subject: [PATCH 30/46] Add dependabot.yml --- .github/dependabot.yml | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b4b65005 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,50 @@ +version: 2 +updates: + # Backend โ€“ Gradle (Java / Spring) + - package-ecosystem: "gradle" + directory: "/booklore-api" + schedule: + interval: "weekly" + day: "friday" + time: "03:00" + timezone: "UTC" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "backend" + commit-message: + prefix: "chore(deps)" + groups: + gradle-dependencies: + patterns: + - "*" + + # Frontend โ€“ npm (Angular) + - package-ecosystem: "npm" + directory: "/booklore-ui" + schedule: + interval: "weekly" + day: "friday" + time: "03:00" + timezone: "UTC" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "frontend" + commit-message: + prefix: "chore(deps)" + groups: + npm-dependencies: + patterns: + - "*" + + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + labels: + - "dependencies" + - "ci" + commit-message: + prefix: "chore(deps)" From a10b0da8e51f4f2681aa77883ff405ac13b5f112 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Dec 2025 22:06:54 -0700 Subject: [PATCH 31/46] chore(deps): bump docker/login-action from 2 to 3 (#1908) Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-build-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index c83b698c..d51257f0 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -107,14 +107,14 @@ jobs: - name: Authenticate to Docker Hub if: github.event_name == 'push' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Authenticate to GitHub Container Registry if: github.event_name == 'push' - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} From 694358fc2f127743cf7bc910ccacf182bbb53b92 Mon Sep 17 00:00:00 2001 From: acx10 Date: Mon, 15 Dec 2025 22:15:54 -0700 Subject: [PATCH 32/46] Switch version bump logic to bump:major/minor/patch labels --- .github/workflows/docker-build-publish.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index d51257f0..0a66c373 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -193,11 +193,11 @@ jobs: labels=$(gh pr view "$pr_number" --json labels --jq '.labels[].name' || echo "") echo "PR labels: $labels" - if echo "$labels" | grep -q 'major'; then + if echo "$labels" | grep -q 'bump:major'; then bump="major" - elif echo "$labels" | grep -q 'minor'; then + elif echo "$labels" | grep -q 'bump:minor'; then bump="minor" - elif echo "$labels" | grep -q 'patch'; then + elif echo "$labels" | grep -q 'bump:patch'; then bump="patch" else last_commit_msg=$(git log -1 --pretty=%B) From 80249b17aa9e6c7d36441a771ee1908618cd7e06 Mon Sep 17 00:00:00 2001 From: acx10 Date: Mon, 15 Dec 2025 23:12:36 -0700 Subject: [PATCH 33/46] Update tag --- .github/workflows/docker-build-publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-build-publish.yml b/.github/workflows/docker-build-publish.yml index 0a66c373..d6a84c78 100644 --- a/.github/workflows/docker-build-publish.yml +++ b/.github/workflows/docker-build-publish.yml @@ -276,7 +276,9 @@ jobs: push: true tags: | booklore/booklore:latest + booklore/booklore:${{ env.new_tag }} ghcr.io/booklore-app/booklore:latest + ghcr.io/booklore-app/booklore:${{ env.new_tag }} build-args: | APP_VERSION=${{ env.new_tag }} APP_REVISION=${{ github.sha }} From 0a5f12f38c3c2a0322cddb8950645aac4a29a3e3 Mon Sep 17 00:00:00 2001 From: Muppetteer Date: Wed, 17 Dec 2025 15:28:26 +1100 Subject: [PATCH 34/46] fix: Bookdrop UI mobile support (#1911) * fix: Bookdrop UI mobile support * fix: Wrap long select option items in mobile mode --- ...okdrop-file-metadata-picker.component.html | 150 +++-- ...okdrop-file-metadata-picker.component.scss | 123 +++- ...bookdrop-file-metadata-picker.component.ts | 26 +- .../bookdrop-file-review.component.html | 552 ++++++++++-------- .../bookdrop-file-review.component.scss | 174 ++++-- .../bookdrop-file-review.component.ts | 3 +- 6 files changed, 666 insertions(+), 362 deletions(-) diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component.html b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component.html index dc5bb36b..f5cb409e 100644 --- a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component.html +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component.html @@ -2,9 +2,9 @@
- +
-

Current Metadata

+

File Metadata

@@ -22,32 +22,31 @@ icon="pi pi-angle-double-left" class="mx-2" [outlined]="true" - pTooltip="Move all fields" + pTooltip="Overwrite all fields with fetched metadata" tooltipPosition="bottom" (onClick)="copyAll()" >
-

Fetched Metadata

+

Fetched

-
- +
+
- + - +
@for (field of metadataFieldsTop; track field) { -
- -
+
+ +
- +
} @for (field of metadataChips; track field) { -
- -
+
+ +
+ class="src w-full" + [ngClass]="{ + 'notneeded': !fetchedMetadata[field.fetchedKey], + }"/>
} @for (field of metadataDescription; track field) { -
- -
+
+ +
+ class="src md:!w-1/2" + disabled + [ngClass]="{ + 'notneeded': !fetchedMetadata[field.fetchedKey], + }">
} @for (field of metadataFieldsBottom; track field) { -
- -
- + +
+ @@ -189,13 +210,22 @@ [ngClass]=" { 'green-outlined-button': isValueCopied(field.controlName) && !hoveredFields[field.controlName], - 'red-outlined-button': isValueCopied(field.controlName) && hoveredFields[field.controlName] + 'red-outlined-button': isValueCopied(field.controlName) && hoveredFields[field.controlName], + 'notneeded' : !fetchedMetadata[field.fetchedKey] }" class="arrow-button" (click)="hoveredFields[field.controlName] && isValueCopied(field.controlName) ? resetField(field.controlName) : copyFetchedToCurrent(field.controlName)" (mouseenter)="onMouseEnter(field.controlName)" (mouseleave)="onMouseLeave(field.controlName)"/> - +
} @@ -203,40 +233,38 @@
} @else { -
+

- - Unable to fetch new metadata for this file + Unable to fetch metadata for this file

-
-
-
- +
+
+ Book Thumbnail
-
+
@for (field of metadataFieldsTop; track field) { -
- +
+
- +
+
@@ -259,17 +287,17 @@ } @for (field of metadataDescription; track field) { -
- +
+
- +
} @for (field of metadataFieldsBottom; track field) { -
- +
+
-
-
-
-

- Review Bookdrop Files - - - -

-

- These files were uploaded to the - Bookdrop Folder. - Review their fetched metadata, assign a library and subpath, and finalize where they belong in your collection. -

-
+
+
+
+

+ Review Bookdrop Files + + + +

+

+ These files were uploaded to the + Bookdrop Folder. + Review their fetched metadata, assign a library and subpath, and finalize where they belong in your collection. +

+
-
- - +
+ + +
+
+
+ @if (loading) { + +
+
+ + Loading Bookdrop files. Please wait...
- @if (loading) { + } @else { -
-
+
+ @if (saving) { +
- Loading Bookdrop files. Please wait... +
+ + Organizing and moving files to their designated libraries. Please wait... + +
-
+ } - } @else { - -
- @if (saving) { -
- -
- - Organizing and moving files to their designated libraries. Please wait... - -
+ @if (bookdropFileUis.length !== 0) { +
+
+ + + + +
- } + +
+ - @if (bookdropFileUis.length !== 0) { -
-
- - - + + + + + + + +
+
+ } +
+ +
+ @if (bookdropFileUis.length === 0) { +
+ No bookdrop files to review. +
+ } @else { + @for (file of bookdropFileUis; track file) { + +
+
+ + + [(ngModel)]="file.selected" + (ngModelChange)="toggleFileSelection(file.file.id, $event)"> - -
- -
- - - - + @if (file.metadataForm.get('thumbnailUrl')?.value) { + Cover + } @else { +
?
+ } - - -
-
- } -
+ @if (file.file.fetchedMetadata?.thumbnailUrl) { + Fetched Cover + } @else { +
?
+ } -
- @if (bookdropFileUis.length === 0) { -
- No bookdrop files to review. -
- } @else { - @for (file of bookdropFileUis; track file) { +
+ {{ file.file.fileName }} +
-
-
- - - - - - - - @if (file.metadataForm.get('thumbnailUrl')?.value) { - Cover - } - - @if (file.file.fetchedMetadata?.thumbnailUrl) { - Fetched Cover - } - -
- {{ file.file.fileName }} -
- - + @@ -174,8 +177,9 @@ [options]="libraryOptions" optionLabel="label" optionValue="value" - placeholder="Select Library" + placeholder="Library" class="library-select" + appendTo="body" [(ngModel)]="file.selectedLibraryId" (onChange)="onLibraryChange(file)"> @@ -185,7 +189,7 @@ [options]="file.availablePaths" optionLabel="name" optionValue="id" - placeholder="Select Subpath" + placeholder="Subpath" class="path-select" appendTo="body" [(ngModel)]="file.selectedPathId"> @@ -193,59 +197,71 @@ + [pTooltip]="file.showDetails + ? 'Hide metadata' + : file.file.fetchedMetadata?.title + ? 'Review and copy fetched metadata' + : 'Show file metadata (no fetched metadata)'" + tooltipPosition="left">
- - @if (file.showDetails) { - - - }
- } + + @if (file.showDetails) { + + + } +
+ } + } +
+ } +
+ + @if (!loading) { + + + + + + + + }
diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.scss b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.scss index e7858dea..5af585f1 100644 --- a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.scss +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.scss @@ -1,7 +1,3 @@ -.container { - overflow-x: auto; -} - .main-card { display: flex; flex-direction: column; @@ -9,22 +5,24 @@ border-radius: 0.75rem; overflow: hidden; background: var(--card-background); - min-width: 60rem; border: 1px solid var(--p-content-border-color); + + @media (max-width: 768px) { + height: calc(100dvh - 4.9rem); + } } .header { - padding: 1.5rem 1rem 1rem; + padding: 1.5rem; display: flex; - flex-direction: column; + flex-direction: row; + align-items: center; + justify-content: space-between; border-bottom: 1px solid var(--p-content-border-color); - margin-bottom: 1.5rem; - - @media (min-width: 768px) { - padding: 1.5rem 1.5rem 1rem; - flex-direction: row; - align-items: center; - justify-content: space-between; + gap: 1rem; + + @media (max-width: 768px) { + padding: 1rem; } } @@ -41,18 +39,24 @@ p { font-size: 0.875rem; color: rgb(156, 163, 175); - + strong { color: var(--primary-color); } } + + @media (max-width: 768px) { + flex-shrink: 1; + + p { + display: none; + } + } } .header-actions { - margin-top: 1rem; - - @media (min-width: 768px) { - margin-top: 0; + @media (max-width: 768px) { + display: flex; } } @@ -62,6 +66,13 @@ font-size: 0.9rem; } +.card-body { + display: flex; + flex: 1; + flex-direction: column; + overflow: auto; +} + .loading-overlay { position: absolute; inset: 0; @@ -82,7 +93,7 @@ } span { - color: rgb(209, 213, 219); + color: var(--text-color-secondary); } p-progressSpinner { @@ -92,7 +103,7 @@ } .controls-section { - padding: 0 1.5rem 1.5rem; + padding: 1rem; margin: 0; border-bottom: 1px solid var(--p-content-border-color); } @@ -135,40 +146,62 @@ justify-content: space-between; align-items: center; gap: 1rem; - padding: 0 0.25rem; + flex-wrap: wrap; + + label { + font-size: 0.875rem; + color: var(--text-color-secondary); + font-weight: 500; + } } .default-controls { display: flex; gap: 1rem; align-items: center; + justify-content: space-between; p-select { - min-width: 8rem; - max-width: 16rem; + width: 8rem; } + + @media (max-width: 768px) { + width: 100%; + + label { + display: none; + } + + p-select { + flex-basis: 50%; + max-width: 250px; + } + } +} + +::ng-deep .p-select-overlay { + @media (max-width: 768px) { + max-width: 70vw; + + .p-select-option { + white-space: normal; + } + } +} + +.default-controls i.pi { + color: var(--p-button-outlined-info-color); } .action-buttons { display: flex; gap: 1rem; align-items: center; - - span { - font-size: 0.875rem; - color: rgb(209, 213, 219); - font-weight: 500; - } } .content-area { flex: 1; - overflow-y: auto; - padding: 1rem; - - > * + * { - margin-top: 0.5rem; - } + padding: 0 0 1px; } .empty-state { @@ -191,9 +224,9 @@ display: flex; align-items: center; gap: 1rem; - border: 1px solid var(--border-color); - border-radius: 0.75rem 0.75rem 0 0; - padding: 0.5rem 1rem; + border-bottom: 1px solid var(--border-color); + padding: 1rem; + flex-wrap: wrap; } .status-indicator { @@ -212,6 +245,17 @@ transform: scale(1.05); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); } + + @media (max-width: 768px) { + display: none !important; + } +} + +div.cover-image { + display: block; + background-color: rgba(255, 255, 255, 0.1); + text-align: center; + line-height: 2rem; } .file-name { @@ -220,7 +264,11 @@ font-size: 0.875rem; overflow: hidden; text-overflow: ellipsis; - white-space: nowrap; + white-space: wrap; + + @media (max-width: 768px) { + max-width: 100%; + } } .metadata-status { @@ -237,34 +285,58 @@ } } +.file-library { + display: flex; + gap: 1rem; + justify-content: space-between; + align-items: center; + + @media (max-width: 768px) { + width: 100%; + } +} + .library-select, .path-select { - min-width: 8rem; - max-width: 16rem; + width: 8rem; + + @media (max-width: 768px) { + flex-basis: 50%; + max-width: 250px; + } } .details-section { - border-left: 1px solid var(--border-color); - border-right: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color); - border-top: none; - border-radius: 0 0 0.625rem 0.625rem; } -.footer { - padding: 0.5rem 1rem; +.footer, .footer-mobile { + padding: 1rem; display: flex; align-items: center; justify-content: space-between; gap: 1rem; } +.footer { + @media (max-width: 768px) { + display: none; + } +} + +.footer-mobile { + flex-wrap: wrap; + + @media (min-width: 768px) { + display: none; + } +} + .footer-left, .footer-right { display: flex; gap: 1rem; align-items: center; - min-width: 10rem; } .footer-left { @@ -280,6 +352,10 @@ flex-grow: 1; display: flex; justify-content: center; + + @media (max-width: 768px) { + width: 100%; + } } .spacer { diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.ts b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.ts index 24945bba..06ccaa05 100644 --- a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.ts +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.ts @@ -21,7 +21,7 @@ import {AppSettingsService} from '../../../../shared/service/app-settings.servic import {BookMetadata} from '../../../book/model/book.model'; import {UrlHelperService} from '../../../../shared/service/url-helper.service'; import {Checkbox} from 'primeng/checkbox'; -import {NgClass, NgStyle} from '@angular/common'; +import {NgClass} from '@angular/common'; import {Paginator} from 'primeng/paginator'; import {ActivatedRoute} from '@angular/router'; import {BookdropFileMetadataPickerComponent} from '../bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component'; @@ -53,7 +53,6 @@ export interface BookdropFileUI { Tooltip, Divider, Checkbox, - NgStyle, NgClass, Paginator, ], From 6df338a0d7fbaef190ff05fea3c5346007edc82a Mon Sep 17 00:00:00 2001 From: CounterClops <19741838+CounterClops@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:27:17 +0800 Subject: [PATCH 35/46] feat(api, ui): add bookdrop bulk edit and metadata pattern extraction (#1846) * feat: add bulk editors for bookdrop * fix: update pattern behaviour and remove redundant frontend logic * fix: clean up pattern extractor * fix: create shared logic to align bulk edit and pattern extract and resolve some minor behaviour issues * fix: date matching pattern and resolve issues with pattern matching to ignore extra trailing data * chore: cleanup tests and code to be cleaner * chore: cleanup autogenerated testing rules * fix: update to use the new dialog launcher service * fix: add boolean null check and data validation on pattern extract api * feat: add bulk edit batching to avoid issues with extremely large import counts * fix: adding timeout to avoid potential redos issue * fix: add try blocks for issues with potential NumberFormatException * fix: update isbn and asin regex to better match spec * fix: improve error handling and logging * fix: make component names consistent with the project * fix: mising import for pattern syntax exception * chore: add additional tests for the bulk edit service * fix: improve accessibility to new ui elements * fix: further improvements to the pattern extractor timeout * fix: improve frontend placeholder validation * fix: add back changes accidently removed by merge --- .../controller/BookdropFileController.java | 27 + .../dto/request/BookdropBulkEditRequest.java | 20 + .../BookdropPatternExtractRequest.java | 16 + .../dto/response/BookdropBulkEditResult.java | 12 + .../BookdropPatternExtractResult.java | 26 + .../bookdrop/BookdropBulkEditService.java | 138 ++++ .../bookdrop/BookdropMetadataHelper.java | 70 ++ .../bookdrop/FilenamePatternExtractor.java | 630 +++++++++++++++++ .../bookdrop/BookdropBulkEditServiceTest.java | 341 ++++++++++ .../FilenamePatternExtractorTest.java | 644 ++++++++++++++++++ .../bookdrop-bulk-edit-dialog.component.html | 118 ++++ .../bookdrop-bulk-edit-dialog.component.scss | 100 +++ .../bookdrop-bulk-edit-dialog.component.ts | 167 +++++ ...bookdrop-file-metadata-picker.component.ts | 9 +- .../bookdrop-file-review.component.html | 23 +- .../bookdrop-file-review.component.ts | 183 ++++- ...rop-finalize-result-dialog.component.html} | 0 ...rop-finalize-result-dialog.component.scss} | 0 ...kdrop-finalize-result-dialog.component.ts} | 6 +- ...drop-pattern-extract-dialog.component.html | 123 ++++ ...drop-pattern-extract-dialog.component.scss | 171 +++++ ...okdrop-pattern-extract-dialog.component.ts | 277 ++++++++ .../bookdrop/service/bookdrop.service.ts | 46 ++ .../services/dialog-launcher.service.ts | 2 +- 24 files changed, 3139 insertions(+), 10 deletions(-) create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/BookdropBulkEditRequest.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/BookdropPatternExtractRequest.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/dto/response/BookdropBulkEditResult.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/model/dto/response/BookdropPatternExtractResult.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropBulkEditService.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropMetadataHelper.java create mode 100644 booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/FilenamePatternExtractor.java create mode 100644 booklore-api/src/test/java/com/adityachandel/booklore/service/bookdrop/BookdropBulkEditServiceTest.java create mode 100644 booklore-api/src/test/java/com/adityachandel/booklore/service/bookdrop/FilenamePatternExtractorTest.java create mode 100644 booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.html create mode 100644 booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.scss create mode 100644 booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.ts rename booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/{bookdrop-finalize-result-dialog-component.html => bookdrop-finalize-result-dialog.component.html} (100%) rename booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/{bookdrop-finalize-result-dialog-component.scss => bookdrop-finalize-result-dialog.component.scss} (100%) rename booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/{bookdrop-finalize-result-dialog-component.ts => bookdrop-finalize-result-dialog.component.ts} (76%) create mode 100644 booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.html create mode 100644 booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.scss create mode 100644 booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.ts diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookdropFileController.java b/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookdropFileController.java index 804fb9ae..c9395942 100644 --- a/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookdropFileController.java +++ b/booklore-api/src/main/java/com/adityachandel/booklore/controller/BookdropFileController.java @@ -2,16 +2,23 @@ package com.adityachandel.booklore.controller; import com.adityachandel.booklore.model.dto.BookdropFile; import com.adityachandel.booklore.model.dto.BookdropFileNotification; +import com.adityachandel.booklore.model.dto.request.BookdropBulkEditRequest; import com.adityachandel.booklore.model.dto.request.BookdropFinalizeRequest; +import com.adityachandel.booklore.model.dto.request.BookdropPatternExtractRequest; import com.adityachandel.booklore.model.dto.request.BookdropSelectionRequest; +import com.adityachandel.booklore.model.dto.response.BookdropBulkEditResult; import com.adityachandel.booklore.model.dto.response.BookdropFinalizeResult; +import com.adityachandel.booklore.model.dto.response.BookdropPatternExtractResult; import com.adityachandel.booklore.service.bookdrop.BookDropService; +import com.adityachandel.booklore.service.bookdrop.BookdropBulkEditService; import com.adityachandel.booklore.service.bookdrop.BookdropMonitoringService; import com.adityachandel.booklore.service.monitoring.MonitoringService; +import com.adityachandel.booklore.service.bookdrop.FilenamePatternExtractor; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -26,6 +33,8 @@ public class BookdropFileController { private final BookDropService bookDropService; private final BookdropMonitoringService monitoringService; + private final FilenamePatternExtractor filenamePatternExtractor; + private final BookdropBulkEditService bookdropBulkEditService; @Operation(summary = "Get bookdrop notification summary", description = "Retrieve a summary of bookdrop file notifications.") @ApiResponse(responseCode = "200", description = "Notification summary returned successfully") @@ -68,4 +77,22 @@ public class BookdropFileController { monitoringService.rescanBookdropFolder(); return ResponseEntity.ok().build(); } + + @Operation(summary = "Extract metadata from filenames using pattern", description = "Parse filenames of selected files using a pattern to extract metadata fields.") + @ApiResponse(responseCode = "200", description = "Pattern extraction completed") + @PostMapping("/files/extract-pattern") + public ResponseEntity extractFromPattern( + @Parameter(description = "Pattern extraction request") @Valid @RequestBody BookdropPatternExtractRequest request) { + BookdropPatternExtractResult result = filenamePatternExtractor.bulkExtract(request); + return ResponseEntity.ok(result); + } + + @Operation(summary = "Bulk edit metadata for selected files", description = "Apply metadata changes to multiple selected files at once.") + @ApiResponse(responseCode = "200", description = "Bulk edit completed") + @PostMapping("/files/bulk-edit") + public ResponseEntity bulkEditMetadata( + @Parameter(description = "Bulk edit request") @Valid @RequestBody BookdropBulkEditRequest request) { + BookdropBulkEditResult result = bookdropBulkEditService.bulkEdit(request); + return ResponseEntity.ok(result); + } } \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/BookdropBulkEditRequest.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/BookdropBulkEditRequest.java new file mode 100644 index 00000000..ca93756f --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/BookdropBulkEditRequest.java @@ -0,0 +1,20 @@ +package com.adityachandel.booklore.model.dto.request; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; +import java.util.List; +import java.util.Set; + +@Data +public class BookdropBulkEditRequest { + @NotNull + private BookMetadata fields; + @NotNull + private Set enabledFields; + private boolean mergeArrays; + private boolean selectAll; + private List excludedIds; + private List selectedIds; +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/BookdropPatternExtractRequest.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/BookdropPatternExtractRequest.java new file mode 100644 index 00000000..58f81b7a --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/request/BookdropPatternExtractRequest.java @@ -0,0 +1,16 @@ +package com.adityachandel.booklore.model.dto.request; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import java.util.List; + +@Data +public class BookdropPatternExtractRequest { + @NotBlank + private String pattern; + private Boolean selectAll; + private List excludedIds; + private List selectedIds; + private Boolean preview; +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/response/BookdropBulkEditResult.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/response/BookdropBulkEditResult.java new file mode 100644 index 00000000..40c9089b --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/response/BookdropBulkEditResult.java @@ -0,0 +1,12 @@ +package com.adityachandel.booklore.model.dto.response; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class BookdropBulkEditResult { + private int totalFiles; + private int successfullyUpdated; + private int failed; +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/response/BookdropPatternExtractResult.java b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/response/BookdropPatternExtractResult.java new file mode 100644 index 00000000..c4ba5582 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/model/dto/response/BookdropPatternExtractResult.java @@ -0,0 +1,26 @@ +package com.adityachandel.booklore.model.dto.response; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class BookdropPatternExtractResult { + private int totalFiles; + private int successfullyExtracted; + private int failed; + private List results; + + @Data + @Builder + public static class FileExtractionResult { + private Long fileId; + private String fileName; + private boolean success; + private BookMetadata extractedMetadata; + private String errorMessage; + } +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropBulkEditService.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropBulkEditService.java new file mode 100644 index 00000000..5b19bcee --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropBulkEditService.java @@ -0,0 +1,138 @@ +package com.adityachandel.booklore.service.bookdrop; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import com.adityachandel.booklore.model.dto.request.BookdropBulkEditRequest; +import com.adityachandel.booklore.model.dto.response.BookdropBulkEditResult; +import com.adityachandel.booklore.model.entity.BookdropFileEntity; +import com.adityachandel.booklore.repository.BookdropFileRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; + +@Slf4j +@Service +@RequiredArgsConstructor +public class BookdropBulkEditService { + + private static final int BATCH_SIZE = 500; + + private final BookdropFileRepository bookdropFileRepository; + private final BookdropMetadataHelper metadataHelper; + + @Transactional + public BookdropBulkEditResult bulkEdit(BookdropBulkEditRequest request) { + List fileIds = metadataHelper.resolveFileIds( + request.isSelectAll(), + request.getExcludedIds(), + request.getSelectedIds() + ); + + return processBulkEditInBatches(fileIds, request); + } + + private BookdropBulkEditResult processBulkEditInBatches(List fileIds, BookdropBulkEditRequest request) { + int totalSuccessCount = 0; + int totalFailedCount = 0; + int totalFiles = fileIds.size(); + + for (int batchStart = 0; batchStart < fileIds.size(); batchStart += BATCH_SIZE) { + int batchEnd = Math.min(batchStart + BATCH_SIZE, fileIds.size()); + + BatchEditResult batchResult = processSingleBatch(fileIds, batchStart, batchEnd, request); + + totalSuccessCount += batchResult.successCount(); + totalFailedCount += batchResult.failureCount(); + + log.debug("Processed batch {}-{} of {}: {} successful, {} failed", + batchStart, batchEnd, totalFiles, batchResult.successCount(), batchResult.failureCount()); + } + + return BookdropBulkEditResult.builder() + .totalFiles(totalFiles) + .successfullyUpdated(totalSuccessCount) + .failed(totalFailedCount) + .build(); + } + + private BatchEditResult processSingleBatch(List allFileIds, int batchStart, int batchEnd, + BookdropBulkEditRequest request) { + List batchIds = allFileIds.subList(batchStart, batchEnd); + List batchFiles = bookdropFileRepository.findAllById(batchIds); + + int successCount = 0; + int failureCount = 0; + Set failedFileIds = new HashSet<>(); + + for (BookdropFileEntity file : batchFiles) { + try { + updateFileMetadata(file, request); + successCount++; + } catch (RuntimeException e) { + log.error("Failed to update metadata for file {} ({}): {}", + file.getId(), file.getFileName(), e.getMessage(), e); + failureCount++; + failedFileIds.add(file.getId()); + } + } + + List filesToSave = batchFiles.stream() + .filter(file -> !failedFileIds.contains(file.getId())) + .toList(); + + if (!filesToSave.isEmpty()) { + bookdropFileRepository.saveAll(filesToSave); + } + + return new BatchEditResult(successCount, failureCount); + } + + private void updateFileMetadata(BookdropFileEntity file, BookdropBulkEditRequest request) { + BookMetadata currentMetadata = metadataHelper.getCurrentMetadata(file); + BookMetadata updates = request.getFields(); + Set enabledFields = request.getEnabledFields(); + boolean mergeArrays = request.isMergeArrays(); + + if (enabledFields.contains("seriesName") && updates.getSeriesName() != null) { + currentMetadata.setSeriesName(updates.getSeriesName()); + } + if (enabledFields.contains("seriesTotal") && updates.getSeriesTotal() != null) { + currentMetadata.setSeriesTotal(updates.getSeriesTotal()); + } + if (enabledFields.contains("publisher") && updates.getPublisher() != null) { + currentMetadata.setPublisher(updates.getPublisher()); + } + if (enabledFields.contains("language") && updates.getLanguage() != null) { + currentMetadata.setLanguage(updates.getLanguage()); + } + + updateArrayField("authors", enabledFields, currentMetadata.getAuthors(), updates.getAuthors(), + currentMetadata::setAuthors, mergeArrays); + updateArrayField("categories", enabledFields, currentMetadata.getCategories(), updates.getCategories(), + currentMetadata::setCategories, mergeArrays); + updateArrayField("moods", enabledFields, currentMetadata.getMoods(), updates.getMoods(), + currentMetadata::setMoods, mergeArrays); + updateArrayField("tags", enabledFields, currentMetadata.getTags(), updates.getTags(), + currentMetadata::setTags, mergeArrays); + + metadataHelper.updateFetchedMetadata(file, currentMetadata); + } + + private void updateArrayField(String fieldName, Set enabledFields, + Set currentValue, Set newValue, + java.util.function.Consumer> setter, boolean mergeArrays) { + if (enabledFields.contains(fieldName) && newValue != null) { + if (mergeArrays && currentValue != null) { + Set merged = new LinkedHashSet<>(currentValue); + merged.addAll(newValue); + setter.accept(merged); + } else { + setter.accept(newValue); + } + } + } + + private record BatchEditResult(int successCount, int failureCount) {} +} \ No newline at end of file diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropMetadataHelper.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropMetadataHelper.java new file mode 100644 index 00000000..b6df3755 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/BookdropMetadataHelper.java @@ -0,0 +1,70 @@ +package com.adityachandel.booklore.service.bookdrop; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import com.adityachandel.booklore.model.entity.BookdropFileEntity; +import com.adityachandel.booklore.repository.BookdropFileRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class BookdropMetadataHelper { + + private final BookdropFileRepository bookdropFileRepository; + private final ObjectMapper objectMapper; + + public List resolveFileIds(boolean selectAll, List excludedIds, List selectedIds) { + if (selectAll) { + List excluded = excludedIds != null ? excludedIds : Collections.emptyList(); + if (excluded.isEmpty()) { + return bookdropFileRepository.findAllIds(); + } else { + return bookdropFileRepository.findAllExcludingIdsFlat(excluded); + } + } + return selectedIds != null ? selectedIds : Collections.emptyList(); + } + + public BookMetadata getCurrentMetadata(BookdropFileEntity file) { + try { + String fetchedMetadataJson = file.getFetchedMetadata(); + if (fetchedMetadataJson != null && !fetchedMetadataJson.isBlank()) { + return objectMapper.readValue(fetchedMetadataJson, BookMetadata.class); + } + } catch (Exception e) { + log.error("Error parsing existing metadata for file {}: {}", file.getId(), e.getMessage()); + } + return new BookMetadata(); + } + + public void updateFetchedMetadata(BookdropFileEntity file, BookMetadata metadata) { + try { + String updatedMetadataJson = objectMapper.writeValueAsString(metadata); + file.setFetchedMetadata(updatedMetadataJson); + } catch (Exception e) { + log.error("Error serializing metadata for file {}: {}", file.getId(), e.getMessage()); + throw new RuntimeException("Failed to update metadata", e); + } + } + + public void mergeMetadata(BookMetadata target, BookMetadata source) { + if (source.getSeriesName() != null) target.setSeriesName(source.getSeriesName()); + if (source.getTitle() != null) target.setTitle(source.getTitle()); + if (source.getSubtitle() != null) target.setSubtitle(source.getSubtitle()); + if (source.getAuthors() != null && !source.getAuthors().isEmpty()) target.setAuthors(source.getAuthors()); + if (source.getSeriesNumber() != null) target.setSeriesNumber(source.getSeriesNumber()); + if (source.getPublishedDate() != null) target.setPublishedDate(source.getPublishedDate()); + if (source.getPublisher() != null) target.setPublisher(source.getPublisher()); + if (source.getLanguage() != null) target.setLanguage(source.getLanguage()); + if (source.getSeriesTotal() != null) target.setSeriesTotal(source.getSeriesTotal()); + if (source.getIsbn10() != null) target.setIsbn10(source.getIsbn10()); + if (source.getIsbn13() != null) target.setIsbn13(source.getIsbn13()); + if (source.getAsin() != null) target.setAsin(source.getAsin()); + } +} diff --git a/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/FilenamePatternExtractor.java b/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/FilenamePatternExtractor.java new file mode 100644 index 00000000..3bdd3136 --- /dev/null +++ b/booklore-api/src/main/java/com/adityachandel/booklore/service/bookdrop/FilenamePatternExtractor.java @@ -0,0 +1,630 @@ +package com.adityachandel.booklore.service.bookdrop; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import com.adityachandel.booklore.model.dto.request.BookdropPatternExtractRequest; +import com.adityachandel.booklore.model.dto.response.BookdropPatternExtractResult; +import com.adityachandel.booklore.model.entity.BookdropFileEntity; +import com.adityachandel.booklore.repository.BookdropFileRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FilenameUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.PreDestroy; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.concurrent.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +@Slf4j +@Service +@RequiredArgsConstructor +public class FilenamePatternExtractor { + + private final BookdropFileRepository bookdropFileRepository; + private final BookdropMetadataHelper metadataHelper; + private final ExecutorService regexExecutor = Executors.newCachedThreadPool(runnable -> { + Thread thread = new Thread(runnable); + thread.setDaemon(true); + return thread; + }); + + private static final int PREVIEW_FILE_LIMIT = 5; + private static final long REGEX_TIMEOUT_SECONDS = 5; + private static final int TWO_DIGIT_YEAR_CUTOFF = 50; + private static final int TWO_DIGIT_YEAR_CENTURY_BASE = 1900; + private static final int FOUR_DIGIT_YEAR_LENGTH = 4; + private static final int TWO_DIGIT_YEAR_LENGTH = 2; + private static final int COMPACT_DATE_LENGTH = 8; + + private static final Map PLACEHOLDER_CONFIGS = Map.ofEntries( + Map.entry("SeriesName", new PlaceholderConfig("(.+?)", "seriesName")), + Map.entry("Title", new PlaceholderConfig("(.+?)", "title")), + Map.entry("Subtitle", new PlaceholderConfig("(.+?)", "subtitle")), + Map.entry("Authors", new PlaceholderConfig("(.+?)", "authors")), + Map.entry("SeriesNumber", new PlaceholderConfig("(\\d+(?:\\.\\d+)?)", "seriesNumber")), + Map.entry("Published", new PlaceholderConfig("(.+?)", "publishedDate")), + Map.entry("Publisher", new PlaceholderConfig("(.+?)", "publisher")), + Map.entry("Language", new PlaceholderConfig("([a-zA-Z]+)", "language")), + Map.entry("SeriesTotal", new PlaceholderConfig("(\\d+)", "seriesTotal")), + Map.entry("ISBN10", new PlaceholderConfig("(\\d{9}[0-9Xx])", "isbn10")), + Map.entry("ISBN13", new PlaceholderConfig("([0-9]{13})", "isbn13")), + Map.entry("ASIN", new PlaceholderConfig("(B[A-Za-z0-9]{9}|\\d{9}[0-9Xx])", "asin")) + ); + + private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{(\\w+)(?::(.*?))?}|\\*"); + + private static final Pattern FOUR_DIGIT_YEAR_PATTERN = Pattern.compile("\\d{4}"); + private static final Pattern TWO_DIGIT_YEAR_PATTERN = Pattern.compile("\\d{2}"); + private static final Pattern COMPACT_DATE_PATTERN = Pattern.compile("\\d{8}"); + private static final Pattern FLEXIBLE_DATE_PATTERN = Pattern.compile("(\\d{1,4})([^\\d])(\\d{1,2})\\2(\\d{1,4})"); + + @Transactional + public BookdropPatternExtractResult bulkExtract(BookdropPatternExtractRequest request) { + List fileIds = metadataHelper.resolveFileIds( + Boolean.TRUE.equals(request.getSelectAll()), + request.getExcludedIds(), + request.getSelectedIds() + ); + + boolean isPreview = Boolean.TRUE.equals(request.getPreview()); + ParsedPattern cachedPattern = parsePattern(request.getPattern()); + + if (cachedPattern == null) { + log.error("Failed to parse pattern: '{}'", request.getPattern()); + return buildEmptyResult(fileIds.size()); + } + + return isPreview + ? processPreviewExtraction(fileIds, cachedPattern) + : processFullExtractionInBatches(fileIds, cachedPattern); + } + + private BookdropPatternExtractResult processPreviewExtraction(List fileIds, ParsedPattern pattern) { + List limitedFileIds = fileIds.size() > PREVIEW_FILE_LIMIT + ? fileIds.subList(0, PREVIEW_FILE_LIMIT) + : fileIds; + + List previewFiles = bookdropFileRepository.findAllById(limitedFileIds); + List results = new ArrayList<>(); + int successCount = 0; + + for (BookdropFileEntity file : previewFiles) { + BookdropPatternExtractResult.FileExtractionResult result = extractFromFile(file, pattern); + results.add(result); + if (result.isSuccess()) { + successCount++; + } + } + + int failureCount = previewFiles.size() - successCount; + + return BookdropPatternExtractResult.builder() + .totalFiles(fileIds.size()) + .successfullyExtracted(successCount) + .failed(failureCount) + .results(results) + .build(); + } + + private BookdropPatternExtractResult processFullExtractionInBatches(List fileIds, ParsedPattern pattern) { + final int BATCH_SIZE = 500; + List allResults = new ArrayList<>(); + int totalSuccessCount = 0; + int totalFailureCount = 0; + int totalFiles = fileIds.size(); + + for (int batchStart = 0; batchStart < fileIds.size(); batchStart += BATCH_SIZE) { + int batchEnd = Math.min(batchStart + BATCH_SIZE, fileIds.size()); + + BatchExtractionResult batchResult = processSingleExtractionBatch(fileIds, batchStart, batchEnd, pattern); + + allResults.addAll(batchResult.results()); + totalSuccessCount += batchResult.successCount(); + totalFailureCount += batchResult.failureCount(); + + log.debug("Processed pattern extraction batch {}-{} of {}: {} successful, {} failed", + batchStart, batchEnd, totalFiles, batchResult.successCount(), batchResult.failureCount()); + } + + return BookdropPatternExtractResult.builder() + .totalFiles(totalFiles) + .successfullyExtracted(totalSuccessCount) + .failed(totalFailureCount) + .results(allResults) + .build(); + } + + private BatchExtractionResult processSingleExtractionBatch(List allFileIds, int batchStart, + int batchEnd, ParsedPattern pattern) { + List batchIds = allFileIds.subList(batchStart, batchEnd); + List batchFiles = bookdropFileRepository.findAllById(batchIds); + List batchResults = new ArrayList<>(); + + for (BookdropFileEntity file : batchFiles) { + BookdropPatternExtractResult.FileExtractionResult result = extractFromFile(file, pattern); + batchResults.add(result); + } + + persistExtractedMetadata(batchResults, batchFiles); + + int successCount = (int) batchResults.stream().filter(BookdropPatternExtractResult.FileExtractionResult::isSuccess).count(); + int failureCount = batchFiles.size() - successCount; + return new BatchExtractionResult(batchResults, successCount, failureCount); + } + + private BookdropPatternExtractResult buildEmptyResult(int totalFiles) { + return BookdropPatternExtractResult.builder() + .totalFiles(totalFiles) + .successfullyExtracted(0) + .failed(totalFiles) + .results(Collections.emptyList()) + .build(); + } + + public BookMetadata extractFromFilename(String filename, String pattern) { + ParsedPattern parsedPattern = parsePattern(pattern); + if (parsedPattern == null) { + return null; + } + + return extractFromFilenameWithParsedPattern(filename, parsedPattern); + } + + private BookMetadata extractFromFilenameWithParsedPattern(String filename, ParsedPattern parsedPattern) { + String nameOnly = FilenameUtils.getBaseName(filename); + + Optional matcherResult = executeRegexMatchingWithTimeout(parsedPattern.compiledPattern(), nameOnly); + + if (matcherResult.isEmpty()) { + return null; + } + + Matcher matcher = matcherResult.get(); + return buildMetadataFromMatch(matcher, parsedPattern.placeholderOrder()); + } + + private Optional executeRegexMatchingWithTimeout(Pattern pattern, String input) { + Future> future = regexExecutor.submit(() -> { + Matcher matcher = pattern.matcher(input); + return matcher.find() ? Optional.of(matcher) : Optional.empty(); + }); + + try { + return future.get(REGEX_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (TimeoutException e) { + future.cancel(true); + log.warn("Pattern matching exceeded {} second timeout for: {}", + REGEX_TIMEOUT_SECONDS, input.substring(0, Math.min(50, input.length()))); + return Optional.empty(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return Optional.empty(); + } catch (ExecutionException e) { + log.error("Pattern matching failed: {}", e.getCause() != null ? e.getCause().getMessage() : "Unknown"); + return Optional.empty(); + } + } + + @PreDestroy + public void shutdownRegexExecutor() { + regexExecutor.shutdown(); + try { + if (!regexExecutor.awaitTermination(5, TimeUnit.SECONDS)) { + regexExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + regexExecutor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + private BookdropPatternExtractResult.FileExtractionResult extractFromFile( + BookdropFileEntity file, + ParsedPattern parsedPattern) { + try { + BookMetadata extracted = extractFromFilenameWithParsedPattern(file.getFileName(), parsedPattern); + + if (extracted == null) { + String errorMsg = "Pattern did not match filename structure. Check if the pattern aligns with the filename format."; + log.debug("Pattern mismatch for file '{}'", file.getFileName()); + return BookdropPatternExtractResult.FileExtractionResult.builder() + .fileId(file.getId()) + .fileName(file.getFileName()) + .success(false) + .errorMessage(errorMsg) + .build(); + } + + return BookdropPatternExtractResult.FileExtractionResult.builder() + .fileId(file.getId()) + .fileName(file.getFileName()) + .success(true) + .extractedMetadata(extracted) + .build(); + + } catch (RuntimeException e) { + String errorMsg = "Extraction failed: " + e.getMessage(); + log.debug("Pattern extraction failed for file '{}': {}", file.getFileName(), e.getMessage()); + return BookdropPatternExtractResult.FileExtractionResult.builder() + .fileId(file.getId()) + .fileName(file.getFileName()) + .success(false) + .errorMessage(errorMsg) + .build(); + } + } + + private ParsedPattern parsePattern(String pattern) { + if (pattern == null || pattern.isBlank()) { + return null; + } + + List placeholderMatches = findAllPlaceholders(pattern); + StringBuilder regexBuilder = new StringBuilder(); + List placeholderOrder = new ArrayList<>(); + int lastEnd = 0; + + for (int i = 0; i < placeholderMatches.size(); i++) { + PlaceholderMatch placeholderMatch = placeholderMatches.get(i); + + String literalTextBeforePlaceholder = pattern.substring(lastEnd, placeholderMatch.start); + regexBuilder.append(Pattern.quote(literalTextBeforePlaceholder)); + + String placeholderName = placeholderMatch.name; + String formatParameter = placeholderMatch.formatParameter; + + boolean isLastPlaceholder = (i == placeholderMatches.size() - 1); + boolean hasTextAfterPlaceholder = (placeholderMatch.end < pattern.length()); + boolean shouldUseGreedyMatching = isLastPlaceholder && !hasTextAfterPlaceholder; + + String regexForPlaceholder; + if ("*".equals(placeholderName)) { + regexForPlaceholder = shouldUseGreedyMatching ? "(.+)" : "(.+?)"; + } else if ("Published".equals(placeholderName) && formatParameter != null) { + regexForPlaceholder = buildRegexForDateFormat(formatParameter); + } else { + PlaceholderConfig config = PLACEHOLDER_CONFIGS.get(placeholderName); + regexForPlaceholder = determineRegexForPlaceholder(config, shouldUseGreedyMatching); + } + + regexBuilder.append(regexForPlaceholder); + + String placeholderWithFormat = formatParameter != null ? placeholderName + ":" + formatParameter : placeholderName; + placeholderOrder.add(placeholderWithFormat); + lastEnd = placeholderMatch.end; + } + + String literalTextAfterLastPlaceholder = pattern.substring(lastEnd); + regexBuilder.append(Pattern.quote(literalTextAfterLastPlaceholder)); + + try { + Pattern compiledPattern = Pattern.compile(regexBuilder.toString()); + return new ParsedPattern(compiledPattern, placeholderOrder); + } catch (PatternSyntaxException e) { + log.error("Invalid regex syntax from user input '{}': {}", pattern, e.getMessage()); + return null; + } + } + + private List findAllPlaceholders(String pattern) { + List placeholderMatches = new ArrayList<>(); + Matcher matcher = PLACEHOLDER_PATTERN.matcher(pattern); + + while (matcher.find()) { + String placeholderName; + String formatParameter = null; + + if (matcher.group(0).equals("*")) { + placeholderName = "*"; + } else { + placeholderName = matcher.group(1); + formatParameter = matcher.group(2); + } + + placeholderMatches.add(new PlaceholderMatch( + matcher.start(), + matcher.end(), + placeholderName, + formatParameter + )); + } + + return placeholderMatches; + } + + private String buildRegexForDateFormat(String dateFormat) { + StringBuilder result = new StringBuilder(); + int i = 0; + + while (i < dateFormat.length()) { + if (dateFormat.startsWith("yyyy", i)) { + result.append("\\d{4}"); + i += 4; + } else if (dateFormat.startsWith("yy", i)) { + result.append("\\d{2}"); + i += 2; + } else if (dateFormat.startsWith("MM", i)) { + result.append("\\d{2}"); + i += 2; + } else if (dateFormat.startsWith("M", i)) { + result.append("\\d{1,2}"); + i += 1; + } else if (dateFormat.startsWith("dd", i)) { + result.append("\\d{2}"); + i += 2; + } else if (dateFormat.startsWith("d", i)) { + result.append("\\d{1,2}"); + i += 1; + } else { + result.append(Pattern.quote(String.valueOf(dateFormat.charAt(i)))); + i++; + } + } + + return "(" + result.toString() + ")"; + } + + private String determineRegexForPlaceholder(PlaceholderConfig config, boolean shouldUseGreedyMatching) { + if (config != null) { + String configuredRegex = config.regex(); + boolean isNonGreedyTextPattern = configuredRegex.equals("(.+?)"); + + if (shouldUseGreedyMatching && isNonGreedyTextPattern) { + return "(.+)"; + } + return configuredRegex; + } + + return shouldUseGreedyMatching ? "(.+)" : "(.+?)"; + } + + private BookMetadata buildMetadataFromMatch(Matcher matcher, List placeholderOrder) { + BookMetadata metadata = new BookMetadata(); + + for (int i = 0; i < placeholderOrder.size(); i++) { + String placeholderWithFormat = placeholderOrder.get(i); + String[] parts = placeholderWithFormat.split(":", 2); + String placeholderName = parts[0]; + String formatParameter = parts.length > 1 ? parts[1] : null; + + if ("*".equals(placeholderName)) { + continue; + } + + String value = matcher.group(i + 1).trim(); + applyValueToMetadata(metadata, placeholderName, value, formatParameter); + } + + return metadata; + } + + private void applyValueToMetadata(BookMetadata metadata, String placeholderName, String value, String formatParameter) { + if (value == null || value.isBlank()) { + return; + } + + switch (placeholderName) { + case "SeriesName" -> metadata.setSeriesName(value); + case "Title" -> metadata.setTitle(value); + case "Subtitle" -> metadata.setSubtitle(value); + case "Authors" -> metadata.setAuthors(parseAuthors(value)); + case "SeriesNumber" -> setSeriesNumber(metadata, value); + case "Published" -> setPublishedDate(metadata, value, formatParameter); + case "Publisher" -> metadata.setPublisher(value); + case "Language" -> metadata.setLanguage(value); + case "SeriesTotal" -> setSeriesTotal(metadata, value); + case "ISBN10" -> metadata.setIsbn10(value); + case "ISBN13" -> metadata.setIsbn13(value); + case "ASIN" -> metadata.setAsin(value); + } + } + + private Set parseAuthors(String value) { + String[] parts = value.split("[,;&]"); + Set authors = new LinkedHashSet<>(); + for (String part : parts) { + String trimmed = part.trim(); + if (!trimmed.isEmpty()) { + authors.add(trimmed); + } + } + return authors; + } + + private void setSeriesNumber(BookMetadata metadata, String value) { + try { + metadata.setSeriesNumber(Float.parseFloat(value)); + } catch (NumberFormatException ignored) { + } + } + + private void setPublishedDate(BookMetadata metadata, String value, String dateFormat) { + String detectedFormat = (dateFormat == null || dateFormat.isBlank()) + ? detectDateFormat(value) + : dateFormat; + + if (detectedFormat == null) { + log.warn("Could not detect date format for value: '{}'", value); + return; + } + + try { + if ("yyyy".equals(detectedFormat) || "yy".equals(detectedFormat)) { + int year = Integer.parseInt(value); + if ("yy".equals(detectedFormat) && year < 100) { + year += (year < TWO_DIGIT_YEAR_CUTOFF) ? 2000 : TWO_DIGIT_YEAR_CENTURY_BASE; + } + metadata.setPublishedDate(LocalDate.of(year, 1, 1)); + return; + } + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(detectedFormat); + LocalDate date = LocalDate.parse(value, formatter); + metadata.setPublishedDate(date); + } catch (NumberFormatException e) { + log.warn("Failed to parse year value '{}': {}", value, e.getMessage()); + } catch (DateTimeParseException e) { + log.warn("Failed to parse date '{}' with format '{}': {}", value, detectedFormat, e.getMessage()); + } catch (IllegalArgumentException e) { + log.warn("Invalid date format '{}' for value '{}': {}", detectedFormat, value, e.getMessage()); + } + } + + private String detectDateFormat(String value) { + if (value == null || value.isBlank()) { + return null; + } + + String trimmed = value.trim(); + int length = trimmed.length(); + + if (length == FOUR_DIGIT_YEAR_LENGTH && FOUR_DIGIT_YEAR_PATTERN.matcher(trimmed).matches()) { + return "yyyy"; + } + + if (length == TWO_DIGIT_YEAR_LENGTH && TWO_DIGIT_YEAR_PATTERN.matcher(trimmed).matches()) { + return "yy"; + } + + if (length == COMPACT_DATE_LENGTH && COMPACT_DATE_PATTERN.matcher(trimmed).matches()) { + return "yyyyMMdd"; + } + + Matcher flexibleMatcher = FLEXIBLE_DATE_PATTERN.matcher(trimmed); + if (flexibleMatcher.matches()) { + String separator = flexibleMatcher.group(2); + return determineFlexibleDateFormat(flexibleMatcher, separator); + } + + return null; + } + + private String determineFlexibleDateFormat(Matcher matcher, String separator) { + String part1 = matcher.group(1); + String part2 = matcher.group(3); + String part3 = matcher.group(4); + + int val1, val2, val3; + try { + val1 = Integer.parseInt(part1); + val2 = Integer.parseInt(part2); + val3 = Integer.parseInt(part3); + } catch (NumberFormatException e) { + return null; + } + + String format1, format2, format3; + + if (isYearValue(part1, val1)) { + format1 = buildYearFormat(part1); + if (val2 <= 12 && val3 > 12) { + format2 = buildMonthFormat(part2); + format3 = buildDayFormat(part3); + } else if (val3 <= 12 && val2 > 12) { + format2 = buildDayFormat(part2); + format3 = buildMonthFormat(part3); + } else { + format2 = buildMonthFormat(part2); + format3 = buildDayFormat(part3); + } + } else if (isYearValue(part3, val3)) { + format3 = buildYearFormat(part3); + if (val1 <= 12 && val2 > 12) { + format1 = buildMonthFormat(part1); + format2 = buildDayFormat(part2); + } else if (val2 <= 12 && val1 > 12) { + format1 = buildDayFormat(part1); + format2 = buildMonthFormat(part2); + } else { + format1 = buildDayFormat(part1); + format2 = buildMonthFormat(part2); + } + } else { + format1 = buildDayFormat(part1); + format2 = buildMonthFormat(part2); + format3 = part3.length() == 2 ? "yy" : "y"; + } + + return format1 + separator + format2 + separator + format3; + } + + private boolean isYearValue(String part, int value) { + return part.length() == 4 || value > 31; + } + + private String buildYearFormat(String part) { + return part.length() == 4 ? "yyyy" : "yy"; + } + + private String buildMonthFormat(String part) { + return part.length() == 2 ? "MM" : "M"; + } + + private String buildDayFormat(String part) { + return part.length() == 2 ? "dd" : "d"; + } + + private void setSeriesTotal(BookMetadata metadata, String value) { + try { + metadata.setSeriesTotal(Integer.parseInt(value)); + } catch (NumberFormatException ignored) { + } + } + + private void persistExtractedMetadata(List results, List files) { + Map fileMap = new HashMap<>(); + for (BookdropFileEntity file : files) { + fileMap.put(file.getId(), file); + } + + Set failedFileIds = new HashSet<>(); + + for (BookdropPatternExtractResult.FileExtractionResult result : results) { + if (!result.isSuccess() || result.getExtractedMetadata() == null) { + continue; + } + + BookdropFileEntity file = fileMap.get(result.getFileId()); + if (file == null) { + continue; + } + + try { + BookMetadata currentMetadata = metadataHelper.getCurrentMetadata(file); + BookMetadata extractedMetadata = result.getExtractedMetadata(); + metadataHelper.mergeMetadata(currentMetadata, extractedMetadata); + metadataHelper.updateFetchedMetadata(file, currentMetadata); + + } catch (RuntimeException e) { + log.error("Error persisting extracted metadata for file {} ({}): {}", + file.getId(), file.getFileName(), e.getMessage(), e); + failedFileIds.add(file.getId()); + result.setSuccess(false); + result.setErrorMessage("Failed to save metadata: " + e.getMessage()); + } + } + + List filesToSave = files.stream() + .filter(file -> !failedFileIds.contains(file.getId())) + .toList(); + + if (!filesToSave.isEmpty()) { + bookdropFileRepository.saveAll(filesToSave); + } + } + + private record PlaceholderConfig(String regex, String metadataField) {} + + private record ParsedPattern(Pattern compiledPattern, List placeholderOrder) {} + + private record PlaceholderMatch(int start, int end, String name, String formatParameter) {} + + private record BatchExtractionResult(List results, + int successCount, int failureCount) {} +} diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/service/bookdrop/BookdropBulkEditServiceTest.java b/booklore-api/src/test/java/com/adityachandel/booklore/service/bookdrop/BookdropBulkEditServiceTest.java new file mode 100644 index 00000000..f4c19626 --- /dev/null +++ b/booklore-api/src/test/java/com/adityachandel/booklore/service/bookdrop/BookdropBulkEditServiceTest.java @@ -0,0 +1,341 @@ +package com.adityachandel.booklore.service.bookdrop; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import com.adityachandel.booklore.model.dto.request.BookdropBulkEditRequest; +import com.adityachandel.booklore.model.dto.response.BookdropBulkEditResult; +import com.adityachandel.booklore.model.entity.BookdropFileEntity; +import com.adityachandel.booklore.repository.BookdropFileRepository; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class BookdropBulkEditServiceTest { + + @Mock + private BookdropFileRepository bookdropFileRepository; + + @Mock + private BookdropMetadataHelper metadataHelper; + + @InjectMocks + private BookdropBulkEditService bulkEditService; + + @Captor + private ArgumentCaptor> filesCaptor; + + private BookdropFileEntity createFileEntity(Long id, String fileName, BookMetadata metadata) { + BookdropFileEntity entity = new BookdropFileEntity(); + entity.setId(id); + entity.setFileName(fileName); + entity.setFilePath("/bookdrop/" + fileName); + return entity; + } + + @BeforeEach + void setUp() { + when(metadataHelper.getCurrentMetadata(any())).thenReturn(new BookMetadata()); + doNothing().when(metadataHelper).updateFetchedMetadata(any(), any()); + } + + @Test + void bulkEdit_WithSingleValueFields_ShouldUpdateTextAndNumericFields() { + BookMetadata existingMetadata = new BookMetadata(); + existingMetadata.setSeriesName("Old Series"); + + BookdropFileEntity file1 = createFileEntity(1L, "file1.cbz", existingMetadata); + BookdropFileEntity file2 = createFileEntity(2L, "file2.cbz", existingMetadata); + + when(metadataHelper.resolveFileIds(false, null, List.of(1L, 2L))) + .thenReturn(List.of(1L, 2L)); + when(bookdropFileRepository.findAllById(anyList())) + .thenReturn(List.of(file1, file2)); + + BookMetadata updates = new BookMetadata(); + updates.setSeriesName("New Series"); + updates.setPublisher("Test Publisher"); + updates.setLanguage("en"); + updates.setSeriesTotal(100); + + BookdropBulkEditRequest request = new BookdropBulkEditRequest(); + request.setFields(updates); + request.setEnabledFields(Set.of("seriesName", "publisher", "language", "seriesTotal")); + request.setMergeArrays(false); + request.setSelectAll(false); + request.setSelectedIds(List.of(1L, 2L)); + + BookdropBulkEditResult result = bulkEditService.bulkEdit(request); + + assertEquals(2, result.getTotalFiles()); + assertEquals(2, result.getSuccessfullyUpdated()); + assertEquals(0, result.getFailed()); + + verify(metadataHelper, times(2)).updateFetchedMetadata(any(), any()); + verify(bookdropFileRepository, times(1)).saveAll(anyList()); + } + + @Test + void bulkEdit_WithArrayFieldsMergeMode_ShouldMergeArrays() { + BookMetadata existingMetadata = new BookMetadata(); + existingMetadata.setAuthors(new LinkedHashSet<>(List.of("Author 1"))); + existingMetadata.setCategories(new LinkedHashSet<>(List.of("Category 1"))); + + when(metadataHelper.getCurrentMetadata(any())).thenReturn(existingMetadata); + + BookdropFileEntity file = createFileEntity(1L, "file.cbz", existingMetadata); + + when(metadataHelper.resolveFileIds(false, null, List.of(1L))) + .thenReturn(List.of(1L)); + when(bookdropFileRepository.findAllById(anyList())) + .thenReturn(List.of(file)); + + BookMetadata updates = new BookMetadata(); + updates.setAuthors(new LinkedHashSet<>(List.of("Author 2"))); + updates.setCategories(new LinkedHashSet<>(List.of("Category 2"))); + + BookdropBulkEditRequest request = new BookdropBulkEditRequest(); + request.setFields(updates); + request.setEnabledFields(Set.of("authors", "categories")); + request.setMergeArrays(true); + request.setSelectAll(false); + request.setSelectedIds(List.of(1L)); + + BookdropBulkEditResult result = bulkEditService.bulkEdit(request); + + assertEquals(1, result.getTotalFiles()); + assertEquals(1, result.getSuccessfullyUpdated()); + assertEquals(0, result.getFailed()); + + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(BookMetadata.class); + verify(metadataHelper).updateFetchedMetadata(any(), metadataCaptor.capture()); + + BookMetadata captured = metadataCaptor.getValue(); + assertTrue(captured.getAuthors().contains("Author 1")); + assertTrue(captured.getAuthors().contains("Author 2")); + assertTrue(captured.getCategories().contains("Category 1")); + assertTrue(captured.getCategories().contains("Category 2")); + } + + @Test + void bulkEdit_WithArrayFieldsReplaceMode_ShouldReplaceArrays() { + BookMetadata existingMetadata = new BookMetadata(); + existingMetadata.setAuthors(new LinkedHashSet<>(List.of("Author 1"))); + + when(metadataHelper.getCurrentMetadata(any())).thenReturn(existingMetadata); + + BookdropFileEntity file = createFileEntity(1L, "file.cbz", existingMetadata); + + when(metadataHelper.resolveFileIds(false, null, List.of(1L))) + .thenReturn(List.of(1L)); + when(bookdropFileRepository.findAllById(anyList())) + .thenReturn(List.of(file)); + + BookMetadata updates = new BookMetadata(); + updates.setAuthors(new LinkedHashSet<>(List.of("Author 2"))); + + BookdropBulkEditRequest request = new BookdropBulkEditRequest(); + request.setFields(updates); + request.setEnabledFields(Set.of("authors")); + request.setMergeArrays(false); + request.setSelectAll(false); + request.setSelectedIds(List.of(1L)); + + bulkEditService.bulkEdit(request); + + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(BookMetadata.class); + verify(metadataHelper).updateFetchedMetadata(any(), metadataCaptor.capture()); + + BookMetadata captured = metadataCaptor.getValue(); + assertFalse(captured.getAuthors().contains("Author 1")); + assertTrue(captured.getAuthors().contains("Author 2")); + assertEquals(1, captured.getAuthors().size()); + } + + @Test + void bulkEdit_WithDisabledFields_ShouldNotUpdateThoseFields() { + BookMetadata existingMetadata = new BookMetadata(); + existingMetadata.setSeriesName("Original Series"); + existingMetadata.setPublisher("Original Publisher"); + + when(metadataHelper.getCurrentMetadata(any())).thenReturn(existingMetadata); + + BookdropFileEntity file = createFileEntity(1L, "file.cbz", existingMetadata); + + when(metadataHelper.resolveFileIds(false, null, List.of(1L))) + .thenReturn(List.of(1L)); + when(bookdropFileRepository.findAllById(anyList())) + .thenReturn(List.of(file)); + + BookMetadata updates = new BookMetadata(); + updates.setSeriesName("New Series"); + updates.setPublisher("New Publisher"); + + BookdropBulkEditRequest request = new BookdropBulkEditRequest(); + request.setFields(updates); + request.setEnabledFields(Set.of("seriesName")); + request.setMergeArrays(false); + request.setSelectAll(false); + request.setSelectedIds(List.of(1L)); + + bulkEditService.bulkEdit(request); + + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(BookMetadata.class); + verify(metadataHelper).updateFetchedMetadata(any(), metadataCaptor.capture()); + + BookMetadata captured = metadataCaptor.getValue(); + assertEquals("New Series", captured.getSeriesName()); + assertEquals("Original Publisher", captured.getPublisher()); + } + + @Test + void bulkEdit_WithSelectAll_ShouldProcessAllFiles() { + BookdropFileEntity file1 = createFileEntity(1L, "file1.cbz", new BookMetadata()); + BookdropFileEntity file2 = createFileEntity(2L, "file2.cbz", new BookMetadata()); + BookdropFileEntity file3 = createFileEntity(3L, "file3.cbz", new BookMetadata()); + + when(metadataHelper.resolveFileIds(true, List.of(2L), null)) + .thenReturn(List.of(1L, 3L)); + when(bookdropFileRepository.findAllById(anyList())) + .thenReturn(List.of(file1, file3)); + + BookMetadata updates = new BookMetadata(); + updates.setLanguage("en"); + + BookdropBulkEditRequest request = new BookdropBulkEditRequest(); + request.setFields(updates); + request.setEnabledFields(Set.of("language")); + request.setMergeArrays(false); + request.setSelectAll(true); + request.setExcludedIds(List.of(2L)); + + BookdropBulkEditResult result = bulkEditService.bulkEdit(request); + + assertEquals(2, result.getTotalFiles()); + assertEquals(2, result.getSuccessfullyUpdated()); + verify(metadataHelper, times(2)).updateFetchedMetadata(any(), any()); + } + + @Test + void bulkEdit_WithOneFileError_ShouldContinueWithOthers() { + BookdropFileEntity file1 = createFileEntity(1L, "file1.cbz", new BookMetadata()); + BookdropFileEntity file2 = createFileEntity(2L, "file2.cbz", new BookMetadata()); + BookdropFileEntity file3 = createFileEntity(3L, "file3.cbz", new BookMetadata()); + + when(metadataHelper.resolveFileIds(false, null, List.of(1L, 2L, 3L))) + .thenReturn(List.of(1L, 2L, 3L)); + when(bookdropFileRepository.findAllById(anyList())) + .thenReturn(List.of(file1, file2, file3)); + + doThrow(new RuntimeException("JSON serialization error")) + .when(metadataHelper).updateFetchedMetadata(eq(file2), any()); + + BookMetadata updates = new BookMetadata(); + updates.setLanguage("en"); + + BookdropBulkEditRequest request = new BookdropBulkEditRequest(); + request.setFields(updates); + request.setEnabledFields(Set.of("language")); + request.setMergeArrays(false); + request.setSelectAll(false); + request.setSelectedIds(List.of(1L, 2L, 3L)); + + BookdropBulkEditResult result = bulkEditService.bulkEdit(request); + + assertEquals(3, result.getTotalFiles()); + assertEquals(2, result.getSuccessfullyUpdated()); + assertEquals(1, result.getFailed()); + + verify(bookdropFileRepository).saveAll(filesCaptor.capture()); + List savedFiles = filesCaptor.getValue(); + assertEquals(2, savedFiles.size()); + assertTrue(savedFiles.stream().anyMatch(f -> f.getId().equals(1L))); + assertTrue(savedFiles.stream().anyMatch(f -> f.getId().equals(3L))); + assertFalse(savedFiles.stream().anyMatch(f -> f.getId().equals(2L))); + } + + @Test + void bulkEdit_WithEmptyEnabledFields_ShouldNotUpdateAnything() { + BookdropFileEntity file = createFileEntity(1L, "file.cbz", new BookMetadata()); + + when(metadataHelper.resolveFileIds(false, null, List.of(1L))) + .thenReturn(List.of(1L)); + when(bookdropFileRepository.findAllById(anyList())) + .thenReturn(List.of(file)); + + BookMetadata updates = new BookMetadata(); + updates.setSeriesName("New Series"); + + BookdropBulkEditRequest request = new BookdropBulkEditRequest(); + request.setFields(updates); + request.setEnabledFields(Collections.emptySet()); + request.setMergeArrays(false); + request.setSelectAll(false); + request.setSelectedIds(List.of(1L)); + + BookdropBulkEditResult result = bulkEditService.bulkEdit(request); + + assertEquals(1, result.getSuccessfullyUpdated()); + + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(BookMetadata.class); + verify(metadataHelper).updateFetchedMetadata(any(), metadataCaptor.capture()); + + assertNull(metadataCaptor.getValue().getSeriesName()); + } + + @Test + void bulkEdit_WithLargeSelection_ShouldProcessInBatches() { + List batch1 = new ArrayList<>(); + List batch2 = new ArrayList<>(); + List batch3 = new ArrayList<>(); + List manyIds = new ArrayList<>(); + + for (long i = 1; i <= 1500; i++) { + manyIds.add(i); + BookdropFileEntity file = createFileEntity(i, "file" + i + ".cbz", new BookMetadata()); + if (i <= 500) { + batch1.add(file); + } else if (i <= 1000) { + batch2.add(file); + } else { + batch3.add(file); + } + } + + when(metadataHelper.resolveFileIds(false, null, manyIds)) + .thenReturn(manyIds); + + when(bookdropFileRepository.findAllById(anyList())) + .thenReturn(batch1, batch2, batch3); + + BookMetadata updates = new BookMetadata(); + updates.setLanguage("en"); + + BookdropBulkEditRequest request = new BookdropBulkEditRequest(); + request.setFields(updates); + request.setEnabledFields(Set.of("language")); + request.setMergeArrays(false); + request.setSelectAll(false); + request.setSelectedIds(manyIds); + + BookdropBulkEditResult result = bulkEditService.bulkEdit(request); + + assertEquals(1500, result.getTotalFiles()); + assertEquals(1500, result.getSuccessfullyUpdated()); + assertEquals(0, result.getFailed()); + + verify(bookdropFileRepository, times(3)).findAllById(anyList()); + verify(bookdropFileRepository, times(3)).saveAll(anyList()); + } +} diff --git a/booklore-api/src/test/java/com/adityachandel/booklore/service/bookdrop/FilenamePatternExtractorTest.java b/booklore-api/src/test/java/com/adityachandel/booklore/service/bookdrop/FilenamePatternExtractorTest.java new file mode 100644 index 00000000..1ed67485 --- /dev/null +++ b/booklore-api/src/test/java/com/adityachandel/booklore/service/bookdrop/FilenamePatternExtractorTest.java @@ -0,0 +1,644 @@ +package com.adityachandel.booklore.service.bookdrop; + +import com.adityachandel.booklore.model.dto.BookMetadata; +import com.adityachandel.booklore.model.dto.request.BookdropPatternExtractRequest; +import com.adityachandel.booklore.model.dto.response.BookdropPatternExtractResult; +import com.adityachandel.booklore.model.entity.BookdropFileEntity; +import com.adityachandel.booklore.repository.BookdropFileRepository; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class FilenamePatternExtractorTest { + + @Mock + private BookdropFileRepository bookdropFileRepository; + + @Mock + private BookdropMetadataHelper metadataHelper; + + @InjectMocks + private FilenamePatternExtractor extractor; + + private BookdropFileEntity createFileEntity(Long id, String fileName) { + BookdropFileEntity entity = new BookdropFileEntity(); + entity.setId(id); + entity.setFileName(fileName); + entity.setFilePath("/bookdrop/" + fileName); + return entity; + } + + @Test + void extractFromFilename_WithSeriesAndChapter_ShouldExtractBoth() { + String filename = "Chronicles of Earth - Ch 25.cbz"; + String pattern = "{SeriesName} - Ch {SeriesNumber}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(25.0f, result.getSeriesNumber()); + } + + @Test + void extractFromFilename_WithVolumeAndIssuePattern_ShouldExtractCorrectly() { + String filename = "Chronicles of Earth Vol.3 (of 150).cbz"; + String pattern = "{SeriesName} Vol.{SeriesNumber} (of {SeriesTotal})"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(3.0f, result.getSeriesNumber()); + assertEquals(150, result.getSeriesTotal()); + } + + @Test + void extractFromFilename_WithPublishedYearPattern_ShouldExtractYear() { + String filename = "Chronicles of Earth (2016) 001.cbz"; + String pattern = "{SeriesName} ({Published:yyyy}) {SeriesNumber}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(2016, result.getPublishedDate().getYear()); + assertEquals(1.0f, result.getSeriesNumber()); + } + + @Test + void extractFromFilename_WithAuthorAndTitle_ShouldExtractBoth() { + String filename = "John Smith - The Lost City.epub"; + String pattern = "{Authors} - {Title}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals(Set.of("John Smith"), result.getAuthors()); + assertEquals("The Lost City", result.getTitle()); + } + + @Test + void extractFromFilename_WithMultipleAuthors_ShouldParseAll() { + String filename = "John Smith, Jane Doe - The Lost City.epub"; + String pattern = "{Authors} - {Title}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertTrue(result.getAuthors().contains("John Smith")); + assertTrue(result.getAuthors().contains("Jane Doe")); + assertEquals("The Lost City", result.getTitle()); + } + + @Test + void extractFromFilename_WithDecimalSeriesNumber_ShouldParseCorrectly() { + String filename = "Chronicles of Earth - Ch 10.5.cbz"; + String pattern = "{SeriesName} - Ch {SeriesNumber}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(10.5f, result.getSeriesNumber()); + } + + @Test + void extractFromFilename_WithNonMatchingPattern_ShouldReturnNull() { + String filename = "Random File Name.pdf"; + String pattern = "{SeriesName} - Ch {SeriesNumber}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNull(result); + } + + @Test + void extractFromFilename_WithNullPattern_ShouldReturnNull() { + String filename = "Test File.pdf"; + + BookMetadata result = extractor.extractFromFilename(filename, null); + + assertNull(result); + } + + @Test + void extractFromFilename_WithEmptyPattern_ShouldReturnNull() { + String filename = "Test File.pdf"; + + BookMetadata result = extractor.extractFromFilename(filename, ""); + + assertNull(result); + } + + @Test + void extractFromFilename_WithPublisherYearAndIssue_ShouldExtractAll() { + String filename = "Epic Press - Chronicles of Earth #001 (2011).cbz"; + String pattern = "{Publisher} - {SeriesName} #{SeriesNumber} ({Published:yyyy})"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Epic Press", result.getPublisher()); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(1.0f, result.getSeriesNumber()); + assertEquals(2011, result.getPublishedDate().getYear()); + } + + @Test + void extractFromFilename_WithLanguageTag_ShouldExtractLanguage() { + String filename = "Chronicles of Earth - Ch 500 [EN].cbz"; + String pattern = "{SeriesName} - Ch {SeriesNumber} [{Language}]"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(500.0f, result.getSeriesNumber()); + assertEquals("EN", result.getLanguage()); + } + + @Test + void bulkExtract_WithPreviewMode_ShouldReturnExtractionResults() { + BookdropFileEntity file1 = createFileEntity(1L, "Chronicles A - Ch 1.cbz"); + BookdropFileEntity file2 = createFileEntity(2L, "Chronicles B - Ch 2.cbz"); + BookdropFileEntity file3 = createFileEntity(3L, "Random Name.cbz"); + + BookdropPatternExtractRequest request = new BookdropPatternExtractRequest(); + request.setPattern("{SeriesName} - Ch {SeriesNumber}"); + request.setSelectAll(false); + request.setSelectedIds(List.of(1L, 2L, 3L)); + request.setPreview(true); + + when(metadataHelper.resolveFileIds(false, null, List.of(1L, 2L, 3L))) + .thenReturn(List.of(1L, 2L, 3L)); + when(bookdropFileRepository.findAllById(anyList())).thenReturn(List.of(file1, file2, file3)); + + BookdropPatternExtractResult result = extractor.bulkExtract(request); + + assertNotNull(result); + assertEquals(3, result.getTotalFiles()); + assertEquals(2, result.getSuccessfullyExtracted()); + assertEquals(1, result.getFailed()); + + var successResults = result.getResults().stream() + .filter(BookdropPatternExtractResult.FileExtractionResult::isSuccess) + .toList(); + assertEquals(2, successResults.size()); + } + + @Test + void bulkExtract_WithFullExtraction_ShouldProcessAndPersistAll() { + BookdropFileEntity file1 = createFileEntity(1L, "Chronicles A - Ch 1.cbz"); + BookdropFileEntity file2 = createFileEntity(2L, "Chronicles B - Ch 2.cbz"); + BookdropFileEntity file3 = createFileEntity(3L, "Random Name.cbz"); + + BookdropPatternExtractRequest request = new BookdropPatternExtractRequest(); + request.setPattern("{SeriesName} - Ch {SeriesNumber}"); + request.setSelectAll(false); + request.setSelectedIds(List.of(1L, 2L, 3L)); + request.setPreview(false); + + when(metadataHelper.resolveFileIds(false, null, List.of(1L, 2L, 3L))) + .thenReturn(List.of(1L, 2L, 3L)); + when(bookdropFileRepository.findAllById(anyList())).thenReturn(List.of(file1, file2, file3)); + when(metadataHelper.getCurrentMetadata(any())).thenReturn(new BookMetadata()); + + BookdropPatternExtractResult result = extractor.bulkExtract(request); + + assertNotNull(result); + assertEquals(3, result.getTotalFiles()); + assertEquals(2, result.getSuccessfullyExtracted()); + assertEquals(1, result.getFailed()); + + // Verify metadata was updated for successful extractions (2 files matched pattern) + verify(metadataHelper, times(2)).updateFetchedMetadata(any(), any()); + // Verify all files were saved (even the one that failed extraction keeps original metadata) + verify(bookdropFileRepository, times(1)).saveAll(anyList()); + } + + @Test + void extractFromFilename_WithSpecialCharacters_ShouldHandleCorrectly() { + String filename = "Chronicles (Special Edition) - Ch 5.cbz"; + String pattern = "{SeriesName} - Ch {SeriesNumber}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles (Special Edition)", result.getSeriesName()); + assertEquals(5.0f, result.getSeriesNumber()); + } + + // ===== Greedy Matching Tests ===== + + @Test + void extractFromFilename_SeriesNameOnly_ShouldCaptureFullName() { + String filename = "Chronicles of Earth.cbz"; + String pattern = "{SeriesName}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + } + + @Test + void extractFromFilename_TitleOnly_ShouldCaptureFullTitle() { + String filename = "The Last Kingdom.epub"; + String pattern = "{Title}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("The Last Kingdom", result.getTitle()); + } + + // ===== Complex Pattern Tests ===== + + @Test + void extractFromFilename_SeriesNumberAndTitle_ShouldExtractBoth() { + String filename = "Chronicles of Earth 01 - The Beginning.epub"; + String pattern = "{SeriesName} {SeriesNumber} - {Title}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(1.0f, result.getSeriesNumber()); + assertEquals("The Beginning", result.getTitle()); + } + + @Test + void extractFromFilename_AuthorSeriesTitleFormat_ShouldExtractAll() { + String filename = "Chronicles of Earth 07 - The Final Battle - John Smith.epub"; + String pattern = "{SeriesName} {SeriesNumber} - {Title} - {Authors}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(7.0f, result.getSeriesNumber()); + assertEquals("The Final Battle", result.getTitle()); + assertEquals(Set.of("John Smith"), result.getAuthors()); + } + + @Test + void extractFromFilename_AuthorTitleYear_ShouldExtractAll() { + String filename = "John Smith - The Lost City (1949).epub"; + String pattern = "{Authors} - {Title} ({Published:yyyy})"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals(Set.of("John Smith"), result.getAuthors()); + assertEquals("The Lost City", result.getTitle()); + assertEquals(1949, result.getPublishedDate().getYear()); + } + + @Test + void extractFromFilename_AuthorWithCommas_ShouldParseProperly() { + String filename = "Smith, John R. - The Lost City.epub"; + String pattern = "{Authors} - {Title}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals(Set.of("Smith", "John R."), result.getAuthors()); + assertEquals("The Lost City", result.getTitle()); + } + + @Test + void extractFromFilename_PartNumberFormat_ShouldExtractCorrectly() { + String filename = "Chronicles of Earth - Part 2 - Rising Darkness.epub"; + String pattern = "{SeriesName} - Part {SeriesNumber} - {Title}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(2.0f, result.getSeriesNumber()); + assertEquals("Rising Darkness", result.getTitle()); + } + + @Test + void extractFromFilename_PublisherBracketFormat_ShouldExtractCorrectly() { + String filename = "[Epic Press] Chronicles of Earth Vol.5 [5 of 20].epub"; + String pattern = "[{Publisher}] {SeriesName} Vol.{SeriesNumber} [* of {SeriesTotal}]"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Epic Press", result.getPublisher()); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(5.0f, result.getSeriesNumber()); + assertEquals(20, result.getSeriesTotal()); + } + + @Test + void extractFromFilename_CalibreStyleFormat_ShouldExtractCorrectly() { + String filename = "Chronicles of Earth 01 The Beginning - John Smith.epub"; + String pattern = "{SeriesName} {SeriesNumber} {Title} - {Authors}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(1.0f, result.getSeriesNumber()); + assertEquals("The Beginning", result.getTitle()); + assertEquals(Set.of("John Smith"), result.getAuthors()); + } + + // ===== New Placeholder Tests ===== + + @Test + void extractFromFilename_WithSubtitle_ShouldExtractBoth() { + String filename = "The Lost City - A Tale of Adventure.epub"; + String pattern = "{Title} - {Subtitle}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("The Lost City", result.getTitle()); + assertEquals("A Tale of Adventure", result.getSubtitle()); + } + + @Test + void extractFromFilename_WithISBN13_ShouldExtractISBN13() { + String filename = "The Lost City [1234567890123].epub"; + String pattern = "{Title} [{ISBN13}]"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("The Lost City", result.getTitle()); + assertEquals("1234567890123", result.getIsbn13()); + } + + @Test + void extractFromFilename_WithISBN10_ShouldExtractCorrectly() { + String filename = "Chronicles of Tomorrow - 0553293354.epub"; + String pattern = "{Title} - {ISBN10}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Tomorrow", result.getTitle()); + assertEquals("0553293354", result.getIsbn10()); + } + + @Test + void extractFromFilename_WithISBN10EndingInX_ShouldExtractCorrectly() { + String filename = "Test Book - 043942089X.epub"; + String pattern = "{Title} - {ISBN10}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Test Book", result.getTitle()); + assertEquals("043942089X", result.getIsbn10()); + } + + @Test + void extractFromFilename_WithASIN_ShouldExtractCorrectly() { + String filename = "Chronicles of Earth - B001234567.epub"; + String pattern = "{Title} - {ASIN}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getTitle()); + assertEquals("B001234567", result.getAsin()); + } + + // ===== Published Date Format Tests ===== + + @Test + void extractFromFilename_WithPublishedDateYYYYMMDD_ShouldExtractCorrectly() { + String filename = "The Lost City - 1925-04-10.epub"; + String pattern = "{Title} - {Published:yyyy-MM-dd}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("The Lost City", result.getTitle()); + assertEquals(1925, result.getPublishedDate().getYear()); + assertEquals(4, result.getPublishedDate().getMonthValue()); + assertEquals(10, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_WithPublishedDateCompact_ShouldExtractCorrectly() { + String filename = "Chronicles of Tomorrow_19650801.epub"; + String pattern = "{Title}_{Published:yyyyMMdd}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Tomorrow", result.getTitle()); + assertEquals(1965, result.getPublishedDate().getYear()); + assertEquals(8, result.getPublishedDate().getMonthValue()); + assertEquals(1, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_WithPublishedDateDots_ShouldExtractCorrectly() { + String filename = "Chronicles of Tomorrow (1951.05.01).epub"; + String pattern = "{Title} ({Published:yyyy.MM.dd})"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Tomorrow", result.getTitle()); + assertEquals(1951, result.getPublishedDate().getYear()); + assertEquals(5, result.getPublishedDate().getMonthValue()); + assertEquals(1, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_WithPublishedDateDashes_ShouldExtractCorrectly() { + String filename = "Chronicles of Earth [05-15-2020].epub"; + String pattern = "{Title} [{Published:MM-dd-yyyy}]"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getTitle()); + assertEquals(2020, result.getPublishedDate().getYear()); + assertEquals(5, result.getPublishedDate().getMonthValue()); + assertEquals(15, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_WithPublishedDateSingleDigits_ShouldExtractCorrectly() { + String filename = "Chronicles of Earth - 2023-1-5.epub"; + String pattern = "{Title} - {Published:yyyy-M-d}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getTitle()); + assertEquals(2023, result.getPublishedDate().getYear()); + assertEquals(1, result.getPublishedDate().getMonthValue()); + assertEquals(5, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_ComplexPatternWithMultiplePlaceholders_ShouldExtractAll() { + String filename = "Chronicles of Earth - The Beginning [1234567890123] - 2020-05-15.epub"; + String pattern = "{SeriesName} - {Title} [{ISBN13}] - {Published:yyyy-MM-dd}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals("The Beginning", result.getTitle()); + assertEquals("1234567890123", result.getIsbn13()); + assertEquals(2020, result.getPublishedDate().getYear()); + assertEquals(5, result.getPublishedDate().getMonthValue()); + assertEquals(15, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_PublishedWithoutFormat_AutoDetectsISODate() { + String filename = "The Lost City (2023-05-15).epub"; + String pattern = "{Title} ({Published})"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("The Lost City", result.getTitle()); + assertEquals(2023, result.getPublishedDate().getYear()); + assertEquals(5, result.getPublishedDate().getMonthValue()); + assertEquals(15, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_PublishedWithoutFormat_AutoDetectsCompactDate() { + String filename = "The Beginning [20231225].epub"; + String pattern = "{Title} [{Published}]"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("The Beginning", result.getTitle()); + assertEquals(2023, result.getPublishedDate().getYear()); + assertEquals(12, result.getPublishedDate().getMonthValue()); + assertEquals(25, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_PublishedWithoutFormat_AutoDetectsYear() { + String filename = "The Lost City (2023).epub"; + String pattern = "{Title} ({Published})"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("The Lost City", result.getTitle()); + assertEquals(2023, result.getPublishedDate().getYear()); + assertEquals(1, result.getPublishedDate().getMonthValue()); + assertEquals(1, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_PublishedWithoutFormat_AutoDetectsTwoDigitYear() { + String filename = "Chronicles of Tomorrow (99).epub"; + String pattern = "{Title} ({Published})"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Tomorrow", result.getTitle()); + assertEquals(1999, result.getPublishedDate().getYear()); + } + + @Test + void extractFromFilename_PublishedWithoutFormat_AutoDetectsFlexibleFormat() { + String filename = "Tomorrow (15|05|2023).epub"; + String pattern = "{Title} ({Published})"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Tomorrow", result.getTitle()); + assertEquals(2023, result.getPublishedDate().getYear()); + assertEquals(5, result.getPublishedDate().getMonthValue()); + assertEquals(15, result.getPublishedDate().getDayOfMonth()); + } + + @Test + void extractFromFilename_WildcardBeforePlaceholder_SkipsUnwantedText() { + String filename = "[Extra] Chronicles of Earth - Ch 42.cbz"; + String pattern = "[*] {SeriesName} - Ch {SeriesNumber}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(42.0f, result.getSeriesNumber()); + } + + @Test + void extractFromFilename_WildcardBetweenPlaceholders_SkipsMiddleText() { + String filename = "The Lost City (extra) John Smith.epub"; + String pattern = "{Title} (*) {Authors}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("The Lost City", result.getTitle()); + assertEquals(Set.of("John Smith"), result.getAuthors()); + } + + @Test + void extractFromFilename_WildcardAtEnd_SkipsTrailingText() { + String filename = "Chronicles of Earth v1 - extra.cbz"; + String pattern = "{SeriesName} v{SeriesNumber} - *"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(1.0f, result.getSeriesNumber()); + } + + @Test + void extractFromFilename_WildcardAtEnd_AllowsPartialMatch() { + String filename = "Chronicles of Earth - Chapter 20.cbz"; + String pattern = "{SeriesName} - * {SeriesNumber}"; + + BookMetadata result = extractor.extractFromFilename(filename, pattern); + + assertNotNull(result); + assertEquals("Chronicles of Earth", result.getSeriesName()); + assertEquals(20.0f, result.getSeriesNumber()); + } + + @Test + void extractFromFilename_WildcardWithVariousPlacements_HandlesCorrectly() { + String filename1 = "Chronicles of Tomorrow - Chapter 8.1 (2025).cbz"; + String pattern1 = "{SeriesName} - * {SeriesNumber}"; + BookMetadata result1 = extractor.extractFromFilename(filename1, pattern1); + assertNotNull(result1); + assertEquals("Chronicles of Tomorrow", result1.getSeriesName()); + assertEquals(8.1f, result1.getSeriesNumber()); + + String filename2 = "Junk - Chapter 20.cbz"; + String pattern2 = "* - Chapter {SeriesNumber}"; + BookMetadata result2 = extractor.extractFromFilename(filename2, pattern2); + assertNotNull(result2); + assertEquals(20.0f, result2.getSeriesNumber()); + } +} + diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.html b/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.html new file mode 100644 index 00000000..e30d53a5 --- /dev/null +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.html @@ -0,0 +1,118 @@ +
+
+ + Select which fields to apply to {{ fileCount }} selected file(s). Only checked fields will be updated. +
+ + + +
+

Text Fields

+
+ @for (field of textFields; track field.name) { +
+ + + + +
+ } +
+
+ + + +
+

Number Fields

+
+ @for (field of numberFields; track field.name) { +
+ + + + +
+ } +
+
+ + + +
+
+

Array Fields

+
+ Mode: + + +
+
+

Type and press Enter to add each item.

+
+ @for (field of chipFields; track field.name) { +
+ + + + + +
+ } +
+
+ + + + +
diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.scss b/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.scss new file mode 100644 index 00000000..d57c1c3d --- /dev/null +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.scss @@ -0,0 +1,100 @@ +.bulk-edit-container { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 1rem; + + .helper-text { + font-size: 0.875rem; + color: var(--text-color-secondary); + display: flex; + align-items: center; + gap: 0.5rem; + margin: 0; + padding: 0.5rem 0; + + i { + font-size: 1rem; + } + } +} + +.info-banner { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: rgba(59, 130, 246, 0.08); + border: 1px solid var(--p-primary-color); + border-radius: 6px; + color: var(--p-text-color); + + i { + font-size: 1.25rem; + color: var(--p-primary-color); + } +} + +.fields-section { + display: flex; + flex-direction: column; + gap: 0.75rem; + + h4 { + margin: 0; + font-size: 0.9rem; + font-weight: 600; + color: var(--p-text-secondary-color); + } +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.merge-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + + .merge-label { + font-size: 0.85rem; + color: var(--p-text-secondary-color); + } +} + +.field-grid { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.field-row { + display: grid; + grid-template-columns: auto 120px 1fr; + gap: 0.75rem; + align-items: center; +} + +.field-label { + font-size: 0.9rem; + font-weight: 500; + color: var(--p-text-color); +} + +.field-input { + width: 100%; +} + +.field-input-small { + max-width: 120px; +} + +.dialog-footer { + display: flex; + justify-content: flex-end; + gap: 0.75rem; + padding-top: 0.5rem; +} diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.ts b/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.ts new file mode 100644 index 00000000..93703aac --- /dev/null +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-bulk-edit-dialog/bookdrop-bulk-edit-dialog.component.ts @@ -0,0 +1,167 @@ +import {Component, inject, OnInit, ChangeDetectorRef} from '@angular/core'; +import {FormControl, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {Button} from 'primeng/button'; +import {Checkbox} from 'primeng/checkbox'; +import {InputText} from 'primeng/inputtext'; +import {AutoComplete} from 'primeng/autocomplete'; +import {Divider} from 'primeng/divider'; +import {SelectButton} from 'primeng/selectbutton'; +import {BookMetadata} from '../../../book/model/book.model'; + +export interface BulkEditResult { + fields: Partial; + enabledFields: Set; + mergeArrays: boolean; +} + +interface BulkEditField { + name: string; + label: string; + type: 'text' | 'chips' | 'number'; + controlName: string; +} + +@Component({ + selector: 'app-bookdrop-bulk-edit-dialog', + standalone: true, + imports: [ + ReactiveFormsModule, + FormsModule, + Button, + Checkbox, + InputText, + AutoComplete, + Divider, + SelectButton, + ], + templateUrl: './bookdrop-bulk-edit-dialog.component.html', + styleUrl: './bookdrop-bulk-edit-dialog.component.scss' +}) +export class BookdropBulkEditDialogComponent implements OnInit { + + private readonly dialogRef = inject(DynamicDialogRef); + private readonly config = inject(DynamicDialogConfig); + private readonly cdr = inject(ChangeDetectorRef); + + fileCount: number = 0; + mergeArrays = true; + + enabledFields = new Set(); + + bulkEditForm = new FormGroup({ + seriesName: new FormControl(''), + seriesTotal: new FormControl(null), + authors: new FormControl([]), + publisher: new FormControl(''), + language: new FormControl(''), + categories: new FormControl([]), + moods: new FormControl([]), + tags: new FormControl([]), + }); + + textFields: BulkEditField[] = [ + {name: 'seriesName', label: 'Series Name', type: 'text', controlName: 'seriesName'}, + {name: 'publisher', label: 'Publisher', type: 'text', controlName: 'publisher'}, + {name: 'language', label: 'Language', type: 'text', controlName: 'language'}, + ]; + + numberFields: BulkEditField[] = [ + {name: 'seriesTotal', label: 'Series Total', type: 'number', controlName: 'seriesTotal'}, + ]; + + chipFields: BulkEditField[] = [ + {name: 'authors', label: 'Authors', type: 'chips', controlName: 'authors'}, + {name: 'categories', label: 'Genres', type: 'chips', controlName: 'categories'}, + {name: 'moods', label: 'Moods', type: 'chips', controlName: 'moods'}, + {name: 'tags', label: 'Tags', type: 'chips', controlName: 'tags'}, + ]; + + mergeOptions = [ + {label: 'Merge', value: true}, + {label: 'Replace', value: false}, + ]; + + ngOnInit(): void { + this.fileCount = this.config.data?.fileCount ?? 0; + this.setupFormValueChangeListeners(); + } + + private setupFormValueChangeListeners(): void { + Object.keys(this.bulkEditForm.controls).forEach(fieldName => { + const control = this.bulkEditForm.get(fieldName); + control?.valueChanges.subscribe(value => { + const hasValue = Array.isArray(value) ? value.length > 0 : (value !== null && value !== '' && value !== undefined); + if (hasValue && !this.enabledFields.has(fieldName)) { + this.enabledFields.add(fieldName); + this.cdr.detectChanges(); + } + }); + }); + } + + onAutoCompleteBlur(fieldName: string, event: Event): void { + const target = event.target as HTMLInputElement; + const inputValue = target?.value?.trim(); + if (inputValue) { + const control = this.bulkEditForm.get(fieldName); + const currentValue = (control?.value as string[]) || []; + if (!currentValue.includes(inputValue)) { + control?.setValue([...currentValue, inputValue]); + } + if (target) { + target.value = ''; + } + } + + if (!this.enabledFields.has(fieldName)) { + const control = this.bulkEditForm.get(fieldName); + const value = control?.value; + if (Array.isArray(value) && value.length > 0) { + this.enabledFields.add(fieldName); + this.cdr.detectChanges(); + } + } + } + + toggleField(fieldName: string): void { + if (this.enabledFields.has(fieldName)) { + this.enabledFields.delete(fieldName); + } else { + this.enabledFields.add(fieldName); + } + } + + isFieldEnabled(fieldName: string): boolean { + return this.enabledFields.has(fieldName); + } + + get hasEnabledFields(): boolean { + return this.enabledFields.size > 0; + } + + cancel(): void { + this.dialogRef.close(null); + } + + apply(): void { + const formValue = this.bulkEditForm.value; + const fields: Partial = {}; + + this.enabledFields.forEach(fieldName => { + const value = formValue[fieldName as keyof typeof formValue]; + + if (value !== undefined && value !== null) { + (fields as Record)[fieldName] = value; + } + }); + + const result: BulkEditResult = { + fields, + enabledFields: new Set(this.enabledFields), + mergeArrays: this.mergeArrays, + }; + + this.dialogRef.close(result); + } +} diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component.ts b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component.ts index 49fb200a..129ab3ea 100644 --- a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component.ts +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-metadata-picker/bookdrop-file-metadata-picker.component.ts @@ -153,8 +153,9 @@ export class BookdropFileMetadataPickerComponent { } } - onAutoCompleteBlur(fieldName: string, event: any) { - const inputValue = event.target.value?.trim(); + onAutoCompleteBlur(fieldName: string, event: Event): void { + const target = event.target as HTMLInputElement; + const inputValue = target?.value?.trim(); if (inputValue) { const currentValue = this.metadataForm.get(fieldName)?.value || []; const values = Array.isArray(currentValue) ? currentValue : @@ -163,7 +164,9 @@ export class BookdropFileMetadataPickerComponent { values.push(inputValue); this.metadataForm.get(fieldName)?.setValue(values); } - event.target.value = ''; + if (target) { + target.value = ''; + } } } diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.html b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.html index 391d01b2..96e46354 100644 --- a/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.html +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-file-review/bookdrop-file-review.component.html @@ -66,6 +66,28 @@ pTooltip="Replace current metadata with fetched metadata on all files" tooltipPosition="top"> + + + +
- { - const payload: any = { + const payload: { selectAll: boolean; excludedIds?: number[]; selectedIds?: number[] } = { selectAll: this.selectAllAcrossPages, }; @@ -583,4 +586,180 @@ export class BookdropFileReviewComponent implements OnInit { } }); } + + openBulkEditDialog(): void { + const selectedFiles = this.getSelectedFiles(); + const totalCount = this.selectAllAcrossPages + ? this.totalRecords - this.excludedFiles.size + : selectedFiles.length; + + if (totalCount === 0) { + this.messageService.add({ + severity: 'warn', + summary: 'No files selected', + detail: 'Please select files to bulk edit.', + }); + return; + } + + const dialogRef = this.dialogLauncherService.openDialog(BookdropBulkEditDialogComponent, { + header: `Bulk Edit ${totalCount} Files`, + width: '600px', + modal: true, + closable: true, + data: {fileCount: totalCount}, + }); + + dialogRef?.onClose.subscribe((result: BulkEditResult | null) => { + if (result) { + this.applyBulkMetadataViaBackend(result); + } + }); + } + + openPatternExtractDialog(): void { + const selectedFiles = this.getSelectedFiles(); + const totalCount = this.selectAllAcrossPages + ? this.totalRecords - this.excludedFiles.size + : selectedFiles.length; + + if (totalCount === 0) { + this.messageService.add({ + severity: 'warn', + summary: 'No files selected', + detail: 'Please select files to extract metadata from.', + }); + return; + } + + const sampleFiles = selectedFiles.slice(0, 5).map(f => f.file.fileName); + const selectedIds = selectedFiles.map(f => f.file.id); + + const dialogRef = this.dialogLauncherService.openDialog(BookdropPatternExtractDialogComponent, { + header: 'Extract Metadata from Filenames', + width: '700px', + modal: true, + closable: true, + data: { + sampleFiles, + fileCount: totalCount, + selectAll: this.selectAllAcrossPages, + excludedIds: Array.from(this.excludedFiles), + selectedIds, + }, + }); + + dialogRef?.onClose.subscribe((result: { results: FileExtractionResult[] } | null) => { + if (result?.results) { + this.applyExtractedMetadata(result.results); + } + }); + } + + private getSelectedFiles(): BookdropFileUI[] { + return Object.values(this.fileUiCache).filter(file => { + if (this.selectAllAcrossPages) { + return !this.excludedFiles.has(file.file.id); + } + return file.selected; + }); + } + + private applyBulkMetadataViaBackend(result: BulkEditResult): void { + const selectedFiles = this.getSelectedFiles(); + const selectedIds = selectedFiles.map(f => f.file.id); + + this.applyBulkMetadataToUI(result, selectedFiles); + + const enabledFieldsArray = Array.from(result.enabledFields); + + const payload: BackendBulkEditRequest = { + fields: result.fields, + enabledFields: enabledFieldsArray, + mergeArrays: result.mergeArrays, + selectAll: this.selectAllAcrossPages, + excludedIds: this.selectAllAcrossPages ? Array.from(this.excludedFiles) : undefined, + selectedIds: !this.selectAllAcrossPages ? selectedIds : undefined, + }; + + this.bookdropService.bulkEditMetadata(payload).subscribe({ + next: (backendResult: BackendBulkEditResult) => { + this.messageService.add({ + severity: 'success', + summary: 'Bulk Edit Applied', + detail: `Updated metadata for ${backendResult.successfullyUpdated} of ${backendResult.totalFiles} file(s).`, + }); + }, + error: (err) => { + console.error('Error applying bulk edit:', err); + this.messageService.add({ + severity: 'error', + summary: 'Bulk Edit Failed', + detail: 'An error occurred while applying bulk edits.', + }); + }, + }); + } + + private applyBulkMetadataToUI(result: BulkEditResult, selectedFiles: BookdropFileUI[]): void { + selectedFiles.forEach(fileUi => { + result.enabledFields.forEach(fieldName => { + const value = result.fields[fieldName as keyof BookMetadata]; + if (value === undefined || value === null) { + return; + } + + if (Array.isArray(value) && value.length === 0) { + return; + } + + const control = fileUi.metadataForm.get(fieldName); + if (!control) { + return; + } + + if (result.mergeArrays && Array.isArray(value)) { + const currentValue = control.value || []; + const merged = [...new Set([...currentValue, ...value])]; + control.setValue(merged); + } else { + control.setValue(value); + } + }); + }); + } + + private applyExtractedMetadata(results: FileExtractionResult[]): void { + let appliedCount = 0; + + results.forEach(result => { + if (!result.success || !result.extractedMetadata) { + return; + } + + const fileUi = this.fileUiCache[result.fileId]; + if (!fileUi) { + return; + } + + Object.entries(result.extractedMetadata).forEach(([key, value]) => { + if (value === null || value === undefined) { + return; + } + + const control = fileUi.metadataForm.get(key); + if (control) { + control.setValue(value); + } + }); + + appliedCount++; + }); + + this.messageService.add({ + severity: 'success', + summary: 'Pattern Extraction Applied', + detail: `Applied extracted metadata to ${appliedCount} file(s).`, + }); + } } diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component.html b/booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog.component.html similarity index 100% rename from booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component.html rename to booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog.component.html diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component.scss b/booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog.component.scss similarity index 100% rename from booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component.scss rename to booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog.component.scss diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component.ts b/booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog.component.ts similarity index 76% rename from booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component.ts rename to booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog.component.ts index 2a05f4f5..3902d087 100644 --- a/booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component.ts +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog.component.ts @@ -4,13 +4,13 @@ import {BookdropFinalizeResult} from '../../service/bookdrop.service'; import {DynamicDialogConfig, DynamicDialogRef} from "primeng/dynamicdialog"; @Component({ - selector: 'app-bookdrop-finalize-result-dialog-component', + selector: 'app-bookdrop-finalize-result-dialog', imports: [ NgClass, DatePipe ], - templateUrl: './bookdrop-finalize-result-dialog-component.html', - styleUrl: './bookdrop-finalize-result-dialog-component.scss' + templateUrl: './bookdrop-finalize-result-dialog.component.html', + styleUrl: './bookdrop-finalize-result-dialog.component.scss' }) export class BookdropFinalizeResultDialogComponent implements OnDestroy { diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.html b/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.html new file mode 100644 index 00000000..900e8679 --- /dev/null +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.html @@ -0,0 +1,123 @@ +
+
+ + + Enter a pattern to extract metadata from filenames of {{ fileCount }} selected file(s). + Use placeholders like {{ '{' }}SeriesName{{ '}' }} to capture values. + +
+ + + +
+

Pattern

+
+ + + +
+
+ +
+

Available Placeholders

+
+ @for (placeholder of availablePlaceholders; track placeholder.name) { + + + } +
+
+ +
+

Common Patterns

+
+ @for (commonPattern of commonPatterns; track commonPattern.pattern) { + + + } +
+
+ + + + @if (previewResults.length > 0) { +
+

Preview (Sample Files)

+
+ @for (preview of previewResults; track preview.fileName) { +
+
+ + {{ preview.fileName }} +
+ @if (preview.success) { +
+ @for (entry of getPreviewEntries(preview); track entry.key) { +
+ {{ entry.key }}: + {{ entry.value }} +
+ } +
+ } @else { +
{{ getErrorMessage(preview) }}
+ } +
+ } +
+
+ + + } + + +
diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.scss b/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.scss new file mode 100644 index 00000000..a89c0cbf --- /dev/null +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.scss @@ -0,0 +1,171 @@ +.pattern-extract-container { + display: flex; + flex-direction: column; + gap: 1rem; + padding: 0.5rem; + max-height: 70vh; + overflow-y: auto; +} + +.info-banner { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.75rem 1rem; + background: rgba(59, 130, 246, 0.08); + border: 1px solid var(--p-primary-color); + border-radius: 6px; + color: var(--p-text-color); + + i { + font-size: 1.25rem; + color: var(--p-primary-color); + margin-top: 2px; + } + + code { + background: rgba(255, 255, 255, 0.1); + padding: 0.1rem 0.3rem; + border-radius: 4px; + font-family: monospace; + } +} + +.pattern-section, +.placeholders-section, +.common-patterns-section, +.preview-section { + display: flex; + flex-direction: column; + gap: 0.5rem; + + h4 { + margin: 0; + font-size: 0.9rem; + font-weight: 600; + color: var(--p-text-secondary-color); + } +} + +.pattern-input-row { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.pattern-input { + flex: 1; + font-family: monospace; +} + +.placeholder-chips { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +:host ::ng-deep .placeholder-chip { + cursor: pointer; + font-family: monospace; + font-size: 0.85rem; + + &:hover { + background-color: var(--p-primary-color); + color: var(--p-primary-contrast-color); + } +} + +.common-pattern-buttons { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.preview-list { + display: flex; + flex-direction: column; + gap: 0.5rem; + max-height: 200px; + overflow-y: auto; +} + +.preview-item { + padding: 0.75rem; + border-radius: 6px; + border: 1px solid var(--p-surface-300); + + &.preview-success { + background-color: rgba(76, 175, 80, 0.1); + border-color: rgba(76, 175, 80, 0.3); + } + + &.preview-failure { + background-color: rgba(244, 67, 54, 0.1); + border-color: rgba(244, 67, 54, 0.3); + } +} + +.preview-filename { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 500; + margin-bottom: 0.5rem; + + i { + font-size: 1rem; + } + + .pi-check-circle { + color: #4caf50; + } + + .pi-times-circle { + color: #f44336; + } +} + +.preview-extracted { + display: flex; + flex-wrap: wrap; + gap: 0.5rem 1rem; + padding-left: 1.5rem; +} + +.extracted-field { + display: flex; + gap: 0.25rem; + font-size: 0.85rem; + + .field-name { + color: var(--p-text-secondary-color); + } + + .field-value { + font-weight: 500; + color: var(--p-primary-color); + } +} + +.preview-error { + padding-left: 1.5rem; + font-size: 0.85rem; + color: var(--p-text-secondary-color); + font-style: italic; +} + +.dialog-footer { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 0.75rem; + padding-top: 0.5rem; +} + +.extracting-indicator { + display: flex; + align-items: center; + gap: 0.5rem; + margin-right: auto; + color: var(--p-text-secondary-color); +} diff --git a/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.ts b/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.ts new file mode 100644 index 00000000..b85299c8 --- /dev/null +++ b/booklore-ui/src/app/features/bookdrop/component/bookdrop-pattern-extract-dialog/bookdrop-pattern-extract-dialog.component.ts @@ -0,0 +1,277 @@ +import {Component, ElementRef, inject, OnInit, ViewChild} from '@angular/core'; +import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms'; +import {DynamicDialogConfig, DynamicDialogRef} from 'primeng/dynamicdialog'; +import {Button} from 'primeng/button'; +import {InputText} from 'primeng/inputtext'; +import {Divider} from 'primeng/divider'; +import {Chip} from 'primeng/chip'; +import {ProgressSpinner} from 'primeng/progressspinner'; +import {BookdropService, PatternExtractResult} from '../../service/bookdrop.service'; +import {MessageService} from 'primeng/api'; +import {NgClass} from '@angular/common'; +import {Tooltip} from 'primeng/tooltip'; + +interface PatternPlaceholder { + name: string; + description: string; + example: string; +} + +interface PreviewResult { + fileName: string; + success: boolean; + preview: Record; + errorMessage?: string; +} + +@Component({ + selector: 'app-bookdrop-pattern-extract-dialog', + standalone: true, + imports: [ + ReactiveFormsModule, + Button, + InputText, + Divider, + Chip, + ProgressSpinner, + NgClass, + Tooltip, + ], + templateUrl: './bookdrop-pattern-extract-dialog.component.html', + styleUrl: './bookdrop-pattern-extract-dialog.component.scss' +}) +export class BookdropPatternExtractDialogComponent implements OnInit { + + private readonly dialogRef = inject(DynamicDialogRef); + private readonly config = inject(DynamicDialogConfig); + private readonly bookdropService = inject(BookdropService); + private readonly messageService = inject(MessageService); + + @ViewChild('patternInput', {static: false}) patternInput?: ElementRef; + + fileCount = 0; + selectAll = false; + excludedIds: number[] = []; + selectedIds: number[] = []; + + isExtracting = false; + previewResults: PreviewResult[] = []; + + patternPlaceholderText = 'e.g., {SeriesName} - Ch {SeriesNumber}'; + spinnerStyle = {width: '24px', height: '24px'}; + + patternForm = new FormGroup({ + pattern: new FormControl('', Validators.required), + }); + + availablePlaceholders: PatternPlaceholder[] = [ + {name: '*', description: 'Wildcard - skips any text (not a metadata field)', example: 'anything'}, + {name: 'SeriesName', description: 'Series or comic name', example: 'Chronicles of Earth'}, + {name: 'Title', description: 'Book title', example: 'The Lost City'}, + {name: 'Subtitle', description: 'Book subtitle', example: 'A Tale of Adventure'}, + {name: 'Authors', description: 'Author name(s)', example: 'John Smith'}, + {name: 'SeriesNumber', description: 'Book number in series', example: '25'}, + {name: 'Published', description: 'Full date with format', example: '{Published:yyyy-MM-dd}'}, + {name: 'Publisher', description: 'Publisher name', example: 'Epic Press'}, + {name: 'Language', description: 'Language code', example: 'en'}, + {name: 'SeriesTotal', description: 'Total books in series', example: '50'}, + {name: 'ISBN10', description: 'ISBN-10 identifier', example: '1234567890'}, + {name: 'ISBN13', description: 'ISBN-13 identifier', example: '1234567890123'}, + {name: 'ASIN', description: 'Amazon ASIN', example: 'B012345678'}, + ]; + + commonPatterns = [ + {label: 'Author - Title', pattern: '{Authors} - {Title}'}, + {label: 'Title - Author', pattern: '{Title} - {Authors}'}, + {label: 'Title (Year)', pattern: '{Title} ({Published:yyyy})'}, + {label: 'Author - Title (Year)', pattern: '{Authors} - {Title} ({Published:yyyy})'}, + {label: 'Series #Number', pattern: '{SeriesName} #{SeriesNumber}'}, + {label: 'Series - Chapter Number', pattern: '{SeriesName} - Chapter {SeriesNumber}'}, + {label: 'Series - Vol Number', pattern: '{SeriesName} - Vol {SeriesNumber}'}, + {label: '[Tag] Series - Chapter Number', pattern: '[*] {SeriesName} - Chapter {SeriesNumber}'}, + {label: 'Title by Author', pattern: '{Title} by {Authors}'}, + {label: 'Series vX (of Total)', pattern: '{SeriesName} v{SeriesNumber} (of {SeriesTotal})'}, + ]; + + ngOnInit(): void { + this.fileCount = this.config.data?.fileCount ?? 0; + this.selectAll = this.config.data?.selectAll ?? false; + this.excludedIds = this.config.data?.excludedIds ?? []; + this.selectedIds = this.config.data?.selectedIds ?? []; + } + + insertPlaceholder(placeholderName: string): void { + const patternControl = this.patternForm.get('pattern'); + const currentPattern = patternControl?.value ?? ''; + const inputElement = this.patternInput?.nativeElement; + + const textToInsert = placeholderName === '*' ? '*' : `{${placeholderName}}`; + + const patternToModify = placeholderName === '*' + ? currentPattern + : this.removeExistingPlaceholder(currentPattern, placeholderName); + + if (inputElement) { + const cursorPosition = this.calculateCursorPosition(inputElement, currentPattern, patternToModify); + const newPattern = this.insertTextAtCursor(patternToModify, textToInsert, cursorPosition); + + patternControl?.setValue(newPattern); + this.focusInputAfterInsertion(inputElement, cursorPosition, textToInsert.length); + } else { + patternControl?.setValue(patternToModify + textToInsert); + } + + this.previewPattern(); + } + + private removeExistingPlaceholder(pattern: string, placeholderName: string): string { + const existingPlaceholderRegex = new RegExp(`\\{${placeholderName}(?::[^}]*)?\\}`, 'g'); + return pattern.replace(existingPlaceholderRegex, ''); + } + + private calculateCursorPosition(inputElement: HTMLInputElement, originalPattern: string, modifiedPattern: string): number { + let cursorPosition = inputElement.selectionStart ?? modifiedPattern.length; + + if (originalPattern !== modifiedPattern) { + const existingPlaceholderRegex = new RegExp(`\\{\\w+(?::[^}]*)?\\}`, 'g'); + const matchBefore = originalPattern.substring(0, cursorPosition).match(existingPlaceholderRegex); + if (matchBefore) { + cursorPosition -= matchBefore.reduce((sum, match) => sum + match.length, 0); + } + cursorPosition = Math.max(0, cursorPosition); + } + + return cursorPosition; + } + + private insertTextAtCursor(pattern: string, text: string, cursorPosition: number): string { + const textBefore = pattern.substring(0, cursorPosition); + const textAfter = pattern.substring(cursorPosition); + return textBefore + text + textAfter; + } + + private focusInputAfterInsertion(inputElement: HTMLInputElement, cursorPosition: number, insertedTextLength: number): void { + setTimeout(() => { + const newCursorPosition = cursorPosition + insertedTextLength; + inputElement.setSelectionRange(newCursorPosition, newCursorPosition); + inputElement.focus(); + }, 0); + } + + applyCommonPattern(pattern: string): void { + this.patternForm.get('pattern')?.setValue(pattern); + this.previewPattern(); + } + + previewPattern(): void { + const pattern = this.patternForm.get('pattern')?.value; + if (!pattern) { + this.previewResults = []; + return; + } + + const request = { + pattern, + selectAll: this.selectAll, + excludedIds: this.excludedIds, + selectedIds: this.selectedIds, + preview: true + }; + + this.bookdropService.extractFromPattern(request).subscribe({ + next: (result) => { + this.previewResults = result.results.map(r => ({ + fileName: r.fileName, + success: r.success, + preview: r.extractedMetadata || {}, + errorMessage: r.errorMessage + })); + }, + error: () => { + this.previewResults = []; + } + }); + } + + cancel(): void { + this.dialogRef.close(null); + } + + extract(): void { + const pattern = this.patternForm.get('pattern')?.value; + if (!pattern) { + return; + } + + this.isExtracting = true; + + const payload = { + pattern, + selectAll: this.selectAll, + excludedIds: this.excludedIds, + selectedIds: this.selectedIds, + preview: false, + }; + + this.bookdropService.extractFromPattern(payload).subscribe({ + next: (result: PatternExtractResult) => { + this.isExtracting = false; + this.messageService.add({ + severity: 'success', + summary: 'Extraction Complete', + detail: `Successfully extracted metadata from ${result.successfullyExtracted} of ${result.totalFiles} files.`, + }); + this.dialogRef.close(result); + }, + error: (err) => { + this.isExtracting = false; + console.error('Pattern extraction failed:', err); + this.messageService.add({ + severity: 'error', + summary: 'Extraction Failed', + detail: 'An error occurred during pattern extraction.', + }); + }, + }); + } + + get hasValidPattern(): boolean { + const pattern: string = this.patternForm.get('pattern')?.value ?? ''; + if (!this.patternForm.valid || !pattern) { + return false; + } + const placeholderRegex = /\{[a-zA-Z0-9_]+(?::[^{}]+)?\}|\*/; + return placeholderRegex.test(pattern); + } + + getPlaceholderLabel(name: string): string { + return name === '*' ? '*' : `{${name}}`; + } + + getPlaceholderTooltip(placeholder: PatternPlaceholder): string { + return `${placeholder.description} (e.g., ${placeholder.example})`; + } + + getPreviewClass(preview: PreviewResult): Record { + return { + 'preview-success': preview.success, + 'preview-failure': !preview.success + }; + } + + getPreviewIconClass(preview: PreviewResult): string { + return preview.success ? 'pi-check-circle' : 'pi-times-circle'; + } + + getPreviewEntries(preview: PreviewResult): Array<{key: string; value: string}> { + return Object.entries(preview.preview).map(([key, value]) => ({key, value})); + } + + getErrorMessage(preview: PreviewResult): string { + return preview.errorMessage || 'Pattern did not match'; + } + + getErrorTooltip(preview: PreviewResult): string { + return preview.success ? '' : (preview.errorMessage || 'Pattern did not match filename structure'); + } +} diff --git a/booklore-ui/src/app/features/bookdrop/service/bookdrop.service.ts b/booklore-ui/src/app/features/bookdrop/service/bookdrop.service.ts index 69a2b7b4..685b4af9 100644 --- a/booklore-ui/src/app/features/bookdrop/service/bookdrop.service.ts +++ b/booklore-ui/src/app/features/bookdrop/service/bookdrop.service.ts @@ -56,6 +56,44 @@ export interface Page { number: number; } +export interface PatternExtractRequest { + pattern: string; + selectAll?: boolean; + excludedIds?: number[]; + selectedIds?: number[]; + preview?: boolean; +} + +export interface FileExtractionResult { + fileId: number; + fileName: string; + success: boolean; + extractedMetadata?: BookMetadata; + errorMessage?: string; +} + +export interface PatternExtractResult { + totalFiles: number; + successfullyExtracted: number; + failed: number; + results: FileExtractionResult[]; +} + +export interface BulkEditRequest { + fields: Partial; + enabledFields: string[]; + mergeArrays: boolean; + selectAll?: boolean; + excludedIds?: number[]; + selectedIds?: number[]; +} + +export interface BulkEditResult { + totalFiles: number; + successfullyUpdated: number; + failed: number; +} + @Injectable({providedIn: 'root'}) export class BookdropService { private readonly url = `${API_CONFIG.BASE_URL}/api/v1/bookdrop`; @@ -76,4 +114,12 @@ export class BookdropService { rescan(): Observable { return this.http.post(`${this.url}/rescan`, {}); } + + extractFromPattern(payload: PatternExtractRequest): Observable { + return this.http.post(`${this.url}/files/extract-pattern`, payload); + } + + bulkEditMetadata(payload: BulkEditRequest): Observable { + return this.http.post(`${this.url}/files/bulk-edit`, payload); + } } diff --git a/booklore-ui/src/app/shared/services/dialog-launcher.service.ts b/booklore-ui/src/app/shared/services/dialog-launcher.service.ts index 8f489212..f74405df 100644 --- a/booklore-ui/src/app/shared/services/dialog-launcher.service.ts +++ b/booklore-ui/src/app/shared/services/dialog-launcher.service.ts @@ -11,7 +11,7 @@ import {CreateUserDialogComponent} from '../../features/settings/user-management import {CreateEmailRecipientDialogComponent} from '../../features/settings/email-v2/create-email-recipient-dialog/create-email-recipient-dialog.component'; import {CreateEmailProviderDialogComponent} from '../../features/settings/email-v2/create-email-provider-dialog/create-email-provider-dialog.component'; import {DirectoryPickerComponent} from '../components/directory-picker/directory-picker.component'; -import {BookdropFinalizeResultDialogComponent} from '../../features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog-component'; +import {BookdropFinalizeResultDialogComponent} from '../../features/bookdrop/component/bookdrop-finalize-result-dialog/bookdrop-finalize-result-dialog.component'; import {BookdropFinalizeResult} from '../../features/bookdrop/service/bookdrop.service'; import {MetadataReviewDialogComponent} from '../../features/metadata/component/metadata-review-dialog/metadata-review-dialog-component'; import {MetadataRefreshType} from '../../features/metadata/model/request/metadata-refresh-type.enum'; From 690dbb0aa293ec74749f24efadf06dd0dd09addd Mon Sep 17 00:00:00 2001 From: ACX <8075870+acx10@users.noreply.github.com> Date: Tue, 16 Dec 2025 23:29:18 -0700 Subject: [PATCH 36/46] Upgrade from Angular 20 to 21 + update other dependencies (#1916) Co-authored-by: acx10 --- booklore-ui/angular.json | 8 +- booklore-ui/package-lock.json | 9005 ++++------------- booklore-ui/package.json | 44 +- .../additional-file-uploader.component.ts | 5 +- .../book-reviews/book-reviews.component.ts | 4 +- .../series-page/series-page.component.html | 176 +- .../series-page/series-page.component.ts | 9 +- .../bulk-metadata-update-component.ts | 5 +- .../cbx-reader/cbx-reader.component.html | 74 +- .../cbx-reader/cbx-reader.component.ts | 4 +- .../koreader-settings-component.ts | 5 +- .../settings/email-v2/email-v2.component.ts | 5 +- .../settings/opds-settings/opds-settings.ts | 5 +- .../directory-picker.component.ts | 5 +- .../download-progress-dialog.component.ts | 4 +- .../live-notification-box.component.ts | 2 +- booklore-ui/src/main.ts | 4 +- 17 files changed, 2285 insertions(+), 7079 deletions(-) diff --git a/booklore-ui/angular.json b/booklore-ui/angular.json index f6fe3f08..e1b8b338 100644 --- a/booklore-ui/angular.json +++ b/booklore-ui/angular.json @@ -22,7 +22,7 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular-devkit/build-angular:application", + "builder": "@angular/build:application", "options": { "outputPath": "dist/booklore", "index": "src/index.html", @@ -91,7 +91,7 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular/build:dev-server", "configurations": { "production": { "buildTarget": "booklore:build:production" @@ -103,10 +103,10 @@ "defaultConfiguration": "development" }, "extract-i18n": { - "builder": "@angular-devkit/build-angular:extract-i18n" + "builder": "@angular/build:extract-i18n" }, "test": { - "builder": "@angular-devkit/build-angular:karma", + "builder": "@angular/build:karma", "options": { "polyfills": [ "zone.js", diff --git a/booklore-ui/package-lock.json b/booklore-ui/package-lock.json index a09c0c33..1cbf2d41 100644 --- a/booklore-ui/package-lock.json +++ b/booklore-ui/package-lock.json @@ -8,17 +8,17 @@ "name": "booklore", "version": "0.0.0", "dependencies": { - "@angular/animations": "^20.3.5", - "@angular/cdk": "^20.2.9", - "@angular/common": "^20.3.5", - "@angular/compiler": "^20.3.5", - "@angular/core": "^20.3.5", - "@angular/forms": "^20.3.5", - "@angular/platform-browser": "^20.3.5", - "@angular/platform-browser-dynamic": "^20.3.5", - "@angular/router": "^20.3.5", + "@angular/animations": "^21.0.5", + "@angular/cdk": "^21.0.3", + "@angular/common": "^21.0.5", + "@angular/compiler": "^21.0.5", + "@angular/core": "^21.0.5", + "@angular/forms": "^21.0.5", + "@angular/platform-browser": "^21.0.5", + "@angular/platform-browser-dynamic": "^21.0.5", + "@angular/router": "^21.0.5", "@iharbeck/ngx-virtual-scroller": "^19.0.1", - "@primeng/themes": "^20.4.0", + "@primeng/themes": "^21.0.2", "@stomp/rx-stomp": "^2.3.0", "@stomp/stompjs": "^7.2.1", "@tweenjs/tween.js": "^25.0.0", @@ -30,10 +30,10 @@ "jwt-decode": "^4.0.0", "ng-lazyload-image": "^9.1.3", "ng2-charts": "^8.0.0", - "ngx-extended-pdf-viewer": "^25.6.1", - "ngx-infinite-scroll": "^20.0.0", + "ngx-extended-pdf-viewer": "^25.6.4", + "ngx-infinite-scroll": "^21.0.0", "primeicons": "^7.0.0", - "primeng": "^20.4.0", + "primeng": "^21.0.2", "quill": "^2.0.3", "rxjs": "^7.8.2", "showdown": "^2.1.0", @@ -43,16 +43,16 @@ "zone.js": "^0.16.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^20.3.5", - "@angular/cli": "^20.3.5", - "@angular/compiler-cli": "^20.3.5", + "@angular/build": "^21.0.3", + "@angular/cli": "^21.0.3", + "@angular/compiler-cli": "^21.0.5", "@tailwindcss/typography": "^0.5.19", "@types/jasmine": "^5.1.13", "@types/showdown": "^2.0.6", - "angular-eslint": "^20.3.5", - "autoprefixer": "^10.4.22", - "eslint": "^9.39.1", - "jasmine-core": "^5.12.1", + "angular-eslint": "^21.1.0", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.2", + "jasmine-core": "^5.13.0", "karma": "^6.4.4", "karma-chrome-launcher": "^3.2.0", "karma-coverage": "^2.2.1", @@ -60,61 +60,61 @@ "karma-jasmine-html-reporter": "^2.1.0", "tailwindcss": "^3.4.17", "typescript": "~5.9.3", - "typescript-eslint": "^8.48.0" + "typescript-eslint": "^8.50.0" } }, "node_modules/@algolia/abtesting": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.1.0.tgz", - "integrity": "sha512-sEyWjw28a/9iluA37KLGu8vjxEIlb60uxznfTUmXImy7H5NvbpSO6yYgmgH5KiD7j+zTUUihiST0jEP12IoXow==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.6.1.tgz", + "integrity": "sha512-wV/gNRkzb7sI9vs1OneG129hwe3Q5zPj7zigz3Ps7M5Lpo2hSorrOnXNodHEOV+yXE/ks4Pd+G3CDFIjFTWhMQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-abtesting": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.35.0.tgz", - "integrity": "sha512-uUdHxbfHdoppDVflCHMxRlj49/IllPwwQ2cQ8DLC4LXr3kY96AHBpW0dMyi6ygkn2MtFCc6BxXCzr668ZRhLBQ==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.40.1.tgz", + "integrity": "sha512-cxKNATPY5t+Mv8XAVTI57altkaPH+DZi4uMrnexPxPHODMljhGYY+GDZyHwv9a+8CbZHcY372OkxXrDMZA4Lnw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.35.0.tgz", - "integrity": "sha512-SunAgwa9CamLcRCPnPHx1V2uxdQwJGqb1crYrRWktWUdld0+B2KyakNEeVn5lln4VyeNtW17Ia7V7qBWyM/Skw==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.40.1.tgz", + "integrity": "sha512-XP008aMffJCRGAY8/70t+hyEyvqqV7YKm502VPu0+Ji30oefrTn2al7LXkITz7CK6I4eYXWRhN6NaIUi65F1OA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.35.0.tgz", - "integrity": "sha512-ipE0IuvHu/bg7TjT2s+187kz/E3h5ssfTtjpg1LbWMgxlgiaZIgTTbyynM7NfpSJSKsgQvCQxWjGUO51WSCu7w==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.40.1.tgz", + "integrity": "sha512-gWfQuQUBtzUboJv/apVGZMoxSaB0M4Imwl1c9Ap+HpCW7V0KhjBddqF2QQt5tJZCOFsfNIgBbZDGsEPaeKUosw==", "dev": true, "license": "MIT", "engines": { @@ -122,151 +122,151 @@ } }, "node_modules/@algolia/client-insights": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.35.0.tgz", - "integrity": "sha512-UNbCXcBpqtzUucxExwTSfAe8gknAJ485NfPN6o1ziHm6nnxx97piIbcBQ3edw823Tej2Wxu1C0xBY06KgeZ7gA==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.40.1.tgz", + "integrity": "sha512-RTLjST/t+lsLMouQ4zeLJq2Ss+UNkLGyNVu+yWHanx6kQ3LT5jv8UvPwyht9s7R6jCPnlSI77WnL80J32ZuyJg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.35.0.tgz", - "integrity": "sha512-/KWjttZ6UCStt4QnWoDAJ12cKlQ+fkpMtyPmBgSS2WThJQdSV/4UWcqCUqGH7YLbwlj3JjNirCu3Y7uRTClxvA==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.40.1.tgz", + "integrity": "sha512-2FEK6bUomBzEYkTKzD0iRs7Ljtjb45rKK/VSkyHqeJnG+77qx557IeSO0qVFE3SfzapNcoytTofnZum0BQ6r3Q==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.35.0.tgz", - "integrity": "sha512-8oCuJCFf/71IYyvQQC+iu4kgViTODbXDk3m7yMctEncRSRV+u2RtDVlpGGfPlJQOrAY7OONwJlSHkmbbm2Kp/w==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.40.1.tgz", + "integrity": "sha512-Nju4NtxAvXjrV2hHZNLKVJLXjOlW6jAXHef/CwNzk1b2qIrCWDO589ELi5ZHH1uiWYoYyBXDQTtHmhaOVVoyXg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.35.0.tgz", - "integrity": "sha512-FfmdHTrXhIduWyyuko1YTcGLuicVbhUyRjO3HbXE4aP655yKZgdTIfMhZ/V5VY9bHuxv/fGEh3Od1Lvv2ODNTg==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.40.1.tgz", + "integrity": "sha512-Mw6pAUF121MfngQtcUb5quZVqMC68pSYYjCRZkSITC085S3zdk+h/g7i6FxnVdbSU6OztxikSDMh1r7Z+4iPlA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/ingestion": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.35.0.tgz", - "integrity": "sha512-gPzACem9IL1Co8mM1LKMhzn1aSJmp+Vp434An4C0OBY4uEJRcqsLN3uLBlY+bYvFg8C8ImwM9YRiKczJXRk0XA==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.40.1.tgz", + "integrity": "sha512-z+BPlhs45VURKJIxsR99NNBWpUEEqIgwt10v/fATlNxc4UlXvALdOsWzaFfe89/lbP5Bu4+mbO59nqBC87ZM/g==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.35.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.35.0.tgz", - "integrity": "sha512-w9MGFLB6ashI8BGcQoVt7iLgDIJNCn4OIu0Q0giE3M2ItNrssvb8C0xuwJQyTy1OFZnemG0EB1OvXhIHOvQwWw==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.40.1.tgz", + "integrity": "sha512-VJMUMbO0wD8Rd2VVV/nlFtLJsOAQvjnVNGkMkspFiFhpBA7s/xJOb+fJvvqwKFUjbKTUA7DjiSi1ljSMYBasXg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.35.0.tgz", - "integrity": "sha512-AhrVgaaXAb8Ue0u2nuRWwugt0dL5UmRgS9LXe0Hhz493a8KFeZVUE56RGIV3hAa6tHzmAV7eIoqcWTQvxzlJeQ==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.40.1.tgz", + "integrity": "sha512-ehvJLadKVwTp9Scg9NfzVSlBKH34KoWOQNTaN8i1Ac64AnO6iH2apJVSP6GOxssaghZ/s8mFQsDH3QIZoluFHA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/client-common": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.35.0.tgz", - "integrity": "sha512-diY415KLJZ6x1Kbwl9u96Jsz0OstE3asjXtJ9pmk1d+5gPuQ5jQyEsgC+WmEXzlec3iuVszm8AzNYYaqw6B+Zw==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.40.1.tgz", + "integrity": "sha512-PbidVsPurUSQIr6X9/7s34mgOMdJnn0i6p+N6Ab+lsNhY5eiu+S33kZEpZwkITYBCIbhzDLOvb7xZD3gDi+USA==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0" + "@algolia/client-common": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.35.0.tgz", - "integrity": "sha512-uydqnSmpAjrgo8bqhE9N1wgcB98psTRRQXcjc4izwMB7yRl9C8uuAQ/5YqRj04U0mMQ+fdu2fcNF6m9+Z1BzDQ==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.40.1.tgz", + "integrity": "sha512-ThZ5j6uOZCF11fMw9IBkhigjOYdXGXQpj6h4k+T9UkZrF2RlKcPynFzDeRgaLdpYk8Yn3/MnFbwUmib7yxj5Lw==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0" + "@algolia/client-common": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.35.0.tgz", - "integrity": "sha512-RgLX78ojYOrThJHrIiPzT4HW3yfQa0D7K+MQ81rhxqaNyNBu4F1r+72LNHYH/Z+y9I1Mrjrd/c/Ue5zfDgAEjQ==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.40.1.tgz", + "integrity": "sha512-H1gYPojO6krWHnUXu/T44DrEun/Wl95PJzMXRcM/szstNQczSbwq6wIFJPI9nyE95tarZfUNU3rgorT+wZ6iCQ==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/client-common": "5.35.0" + "@algolia/client-common": "5.40.1" }, "engines": { "node": ">= 14.0.0" @@ -299,13 +299,13 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.2003.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2003.12.tgz", - "integrity": "sha512-5H40lAFF4CKY32C4HOp6bTlOF1f4WsGCwe7FjFQp9A+T7yoCBiHpIWt2JKTwV4sBoTKVDZOnuf0GG+UVKjQT4A==", + "version": "0.2100.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.2100.3.tgz", + "integrity": "sha512-PcruWF0+IxXOTZd9MN/3y4A5aTfblALzT/+zWym26PtisaBgWQ3tRPQsf/CgT8EdmZl8eUOAWlNBSkbUj/S/lQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.12", + "@angular-devkit/core": "21.0.3", "rxjs": "7.8.2" }, "engines": { @@ -314,515 +314,10 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/build-angular": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-20.3.12.tgz", - "integrity": "sha512-HPepPbJA5vprYTWJaSCfpk0s1bPT6Ui6VjFOSb9oY+p9iq+MGkuB1I+swNcRcMLttyMD+FpbMd27F8jSeX5XVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.12", - "@angular-devkit/build-webpack": "0.2003.12", - "@angular-devkit/core": "20.3.12", - "@angular/build": "20.3.12", - "@babel/core": "7.28.3", - "@babel/generator": "7.28.3", - "@babel/helper-annotate-as-pure": "7.27.3", - "@babel/helper-split-export-declaration": "7.24.7", - "@babel/plugin-transform-async-generator-functions": "7.28.0", - "@babel/plugin-transform-async-to-generator": "7.27.1", - "@babel/plugin-transform-runtime": "7.28.3", - "@babel/preset-env": "7.28.3", - "@babel/runtime": "7.28.3", - "@discoveryjs/json-ext": "0.6.3", - "@ngtools/webpack": "20.3.12", - "ansi-colors": "4.1.3", - "autoprefixer": "10.4.21", - "babel-loader": "10.0.0", - "browserslist": "^4.21.5", - "copy-webpack-plugin": "13.0.1", - "css-loader": "7.1.2", - "esbuild-wasm": "0.25.9", - "fast-glob": "3.3.3", - "http-proxy-middleware": "3.0.5", - "istanbul-lib-instrument": "6.0.3", - "jsonc-parser": "3.3.1", - "karma-source-map-support": "1.4.0", - "less": "4.4.0", - "less-loader": "12.3.0", - "license-webpack-plugin": "4.0.2", - "loader-utils": "3.3.1", - "mini-css-extract-plugin": "2.9.4", - "open": "10.2.0", - "ora": "8.2.0", - "picomatch": "4.0.3", - "piscina": "5.1.3", - "postcss": "8.5.6", - "postcss-loader": "8.1.1", - "resolve-url-loader": "5.0.0", - "rxjs": "7.8.2", - "sass": "1.90.0", - "sass-loader": "16.0.5", - "semver": "7.7.2", - "source-map-loader": "5.0.0", - "source-map-support": "0.5.21", - "terser": "5.43.1", - "tree-kill": "1.2.2", - "tslib": "2.8.1", - "webpack": "5.101.2", - "webpack-dev-middleware": "7.4.2", - "webpack-dev-server": "5.2.2", - "webpack-merge": "6.0.1", - "webpack-subresource-integrity": "5.1.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "optionalDependencies": { - "esbuild": "0.25.9" - }, - "peerDependencies": { - "@angular/compiler-cli": "^20.0.0", - "@angular/core": "^20.0.0", - "@angular/localize": "^20.0.0", - "@angular/platform-browser": "^20.0.0", - "@angular/platform-server": "^20.0.0", - "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.12", - "@web/test-runner": "^0.20.0", - "browser-sync": "^3.0.2", - "jest": "^29.5.0 || ^30.2.0", - "jest-environment-jsdom": "^29.5.0 || ^30.2.0", - "karma": "^6.3.0", - "ng-packagr": "^20.0.0", - "protractor": "^7.0.0", - "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", - "typescript": ">=5.8 <6.0" - }, - "peerDependenciesMeta": { - "@angular/core": { - "optional": true - }, - "@angular/localize": { - "optional": true - }, - "@angular/platform-browser": { - "optional": true - }, - "@angular/platform-server": { - "optional": true - }, - "@angular/service-worker": { - "optional": true - }, - "@angular/ssr": { - "optional": true - }, - "@web/test-runner": { - "optional": true - }, - "browser-sync": { - "optional": true - }, - "jest": { - "optional": true - }, - "jest-environment-jsdom": { - "optional": true - }, - "karma": { - "optional": true - }, - "ng-packagr": { - "optional": true - }, - "protractor": { - "optional": true - }, - "tailwindcss": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "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.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/build-angular/node_modules/less": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/less/-/less-4.4.0.tgz", - "integrity": "sha512-kdTwsyRuncDfjEs0DlRILWNvxhDG/Zij4YLO4TMJgDLW+8OzpfkdPnRgrsRuY1o+oaxJGWsps5f/RVBgGmmN0w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "copy-anything": "^2.0.1", - "parse-node-version": "^1.0.1", - "tslib": "^2.3.0" - }, - "bin": { - "lessc": "bin/lessc" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "errno": "^0.1.1", - "graceful-fs": "^4.1.2", - "image-size": "~0.5.0", - "make-dir": "^2.1.0", - "mime": "^1.4.1", - "needle": "^3.1.0", - "source-map": "~0.6.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "optional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/sass": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", - "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/terser": { - "version": "5.43.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", - "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular-devkit/build-angular/node_modules/webpack": { - "version": "5.101.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.2.tgz", - "integrity": "sha512-4JLXU0tD6OZNVqlwzm3HGEhAHufSiyv+skb7q0d2367VDMzrU1Q/ZeepvkcHH0rZie6uqEtTQQe0OEOOluH3Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/@angular-devkit/build-webpack": { - "version": "0.2003.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.2003.12.tgz", - "integrity": "sha512-IkhCU0nAsXYBQOfHu2gQBcYBKhaV1c8wYtu7MmelBcN/iUrG8hRf1sZx+ppUgsdZuBYxCiDiLpcfRVRCIASkvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@angular-devkit/architect": "0.2003.12", - "rxjs": "7.8.2" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "webpack": "^5.30.0", - "webpack-dev-server": "^5.0.2" - } - }, "node_modules/@angular-devkit/core": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.3.12.tgz", - "integrity": "sha512-ReFxd/UOoVDr3+kIUjmYILQZF89qg62POdY7a7OqBH7plmInFlYVSEDouJvGqj3LVCPiqTk2ZOSChbhS/eLxXA==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-21.0.3.tgz", + "integrity": "sha512-X1y3GMYru9+Vt7vz+R8SFAEmDtgf0aZ+1JOpiE7ubHsQOnhA++Pb94HBjQ6CHqlUhQli/XPOBksKNdZkpup8rQ==", "dev": true, "license": "MIT", "dependencies": { @@ -848,16 +343,16 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.3.12.tgz", - "integrity": "sha512-JqJ1u59y+Ud51k/8MHYzSP+aQOeC2PJBaDmMnvqfWVaIt6n3x4gc/VtuhqhpJ0SKulbFuOWgAfI6QbPFrgUYQQ==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-21.0.3.tgz", + "integrity": "sha512-E/Nja+RIyMzjqLXREOnTRwv7GMrycpAD7kGwDg7l8cWrNQ7phqBZcXAt74Jv9K9aYsOC8tw2Ms9t59aQ6iow8w==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.12", + "@angular-devkit/core": "21.0.3", "jsonc-parser": "3.3.1", - "magic-string": "0.30.17", - "ora": "8.2.0", + "magic-string": "0.30.19", + "ora": "9.0.0", "rxjs": "7.8.2" }, "engines": { @@ -867,36 +362,37 @@ } }, "node_modules/@angular-eslint/builder": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-20.7.0.tgz", - "integrity": "sha512-qgf4Cfs1z0VsVpzF/OnxDRvBp60OIzeCsp4mzlckWYVniKo19EPIN6kFDol5eTAIOMPgiBQlMIwgQMHgocXEig==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-21.1.0.tgz", + "integrity": "sha512-pcUlDkGqeZ+oQC0oEjnkDDlB96gbgHQhnBUKdhYAiAOSuiBod4+npP0xQOq5chYtRNPBprhDqgrJrp5DBeDMOA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": ">= 0.2000.0 < 0.2100.0", - "@angular-devkit/core": ">= 20.0.0 < 21.0.0" + "@angular-devkit/architect": ">= 0.2100.0 < 0.2200.0", + "@angular-devkit/core": ">= 21.0.0 < 22.0.0" }, "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*" } }, "node_modules/@angular-eslint/bundled-angular-compiler": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-20.7.0.tgz", - "integrity": "sha512-9KPz24YoiL0SvTtTX6sd1zmysU5cKOCcmpEiXkCoO3L2oYZGlVxmMT4hfSaHMt8qmfvV2KzQMoR6DZM84BwRzQ==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-21.1.0.tgz", + "integrity": "sha512-t52J6FszgEHaJ+IjuzU9qaWfVxsjlVNkAP+B5z2t4NDgbbDDsmI+QJh0OtP1qdlqzjh2pbocEml30KhYmNZm/Q==", "dev": true, "license": "MIT" }, "node_modules/@angular-eslint/eslint-plugin": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-20.7.0.tgz", - "integrity": "sha512-aHH2YTiaonojsKN+y2z4IMugCwdsH/dYIjYBig6kfoSPyf9rGK4zx+gnNGq/pGRjF3bOYrmFgIviYpQVb80inQ==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-21.1.0.tgz", + "integrity": "sha512-oNp+4UzN2M3KwGwEw03NUdXz93vqJd9sMzTbGXWF9+KVfA2LjckGDTrI6g6asGcJMdyTo07rDcnw0m0MkLB5VA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "20.7.0", - "@angular-eslint/utils": "20.7.0", + "@angular-eslint/bundled-angular-compiler": "21.1.0", + "@angular-eslint/utils": "21.1.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { @@ -906,19 +402,19 @@ } }, "node_modules/@angular-eslint/eslint-plugin-template": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-20.7.0.tgz", - "integrity": "sha512-WFmvW2vBR6ExsSKEaActQTteyw6ikWyuJau9XmWEPFd+2eusEt/+wO21ybjDn3uc5FTp1IcdhfYy+U5OdDjH5w==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-21.1.0.tgz", + "integrity": "sha512-FlbRfOCn8IUHvP1ebcCSQFVNh+4X/HqZqL7SW5oj9WIYPiOX9ijS03ndNbfX/pBPSIi8GHLKMjLt8zIy1l5Lww==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "20.7.0", - "@angular-eslint/utils": "20.7.0", + "@angular-eslint/bundled-angular-compiler": "21.1.0", + "@angular-eslint/utils": "21.1.0", "aria-query": "5.3.2", "axobject-query": "4.1.0" }, "peerDependencies": { - "@angular-eslint/template-parser": "20.7.0", + "@angular-eslint/template-parser": "21.1.0", "@typescript-eslint/types": "^7.11.0 || ^8.0.0", "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", @@ -926,29 +422,32 @@ } }, "node_modules/@angular-eslint/schematics": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-20.7.0.tgz", - "integrity": "sha512-S0onfRipDUIL6gFGTFjiWwUDhi42XYrBoi3kJ3wBbKBeIgYv9SP1ppTKDD4ZoDaDU9cQE8nToX7iPn9ifMw6eQ==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-21.1.0.tgz", + "integrity": "sha512-Hal1mYwx4MTjCcNHqfIlua31xrk2tZJoyTiXiGQ21cAeK4sFuY+9V7/8cxbwJMGftX0G4J7uhx8woOdIFuqiZw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": ">= 20.0.0 < 21.0.0", - "@angular-devkit/schematics": ">= 20.0.0 < 21.0.0", - "@angular-eslint/eslint-plugin": "20.7.0", - "@angular-eslint/eslint-plugin-template": "20.7.0", + "@angular-devkit/core": ">= 21.0.0 < 22.0.0", + "@angular-devkit/schematics": ">= 21.0.0 < 22.0.0", + "@angular-eslint/eslint-plugin": "21.1.0", + "@angular-eslint/eslint-plugin-template": "21.1.0", "ignore": "7.0.5", "semver": "7.7.3", "strip-json-comments": "3.1.1" + }, + "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0" } }, "node_modules/@angular-eslint/template-parser": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-20.7.0.tgz", - "integrity": "sha512-CVskZnF38IIxVVlKWi1VCz7YH/gHMJu2IY9bD1AVoBBGIe0xA4FRXJkW2Y+EDs9vQqZTkZZljhK5gL65Ro1PeQ==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-21.1.0.tgz", + "integrity": "sha512-PYVgNbjNtuD5/QOuS6cHR8A7bRqsVqxtUUXGqdv76FYMAajQcAvyfR0QxOkqf3NmYxgNgO3hlUHWq0ILjVbcow==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "20.7.0", + "@angular-eslint/bundled-angular-compiler": "21.1.0", "eslint-scope": "^9.0.0" }, "peerDependencies": { @@ -957,13 +456,13 @@ } }, "node_modules/@angular-eslint/utils": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-20.7.0.tgz", - "integrity": "sha512-B6EJHbsk2W/lnS3kS/gm56VGvX735419z/DzgbRDcOvqMGMLwD1ILzv5OTEcL1rzpnB0AHW+IxOu6y/aCzSNUA==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-21.1.0.tgz", + "integrity": "sha512-rWINgxGREu+NFUPCpAVsBGG8B4hfXxyswM0N5GbjykvsfB5W6PUix2Gsoh++iEsZPT+c9lvgXL5GbpwfanjOow==", "dev": true, "license": "MIT", "dependencies": { - "@angular-eslint/bundled-angular-compiler": "20.7.0" + "@angular-eslint/bundled-angular-compiler": "21.1.0" }, "peerDependencies": { "@typescript-eslint/utils": "^7.11.0 || ^8.0.0", @@ -972,9 +471,9 @@ } }, "node_modules/@angular/animations": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.14.tgz", - "integrity": "sha512-Sx3/XNu2rR+R8T8JkJEaIpZDZPk0IecS0Ayt6HTanNUZXuw0HVou3vkjR5B2St5nM4MXs0gh+S6aLNuArtqJTQ==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-21.0.5.tgz", + "integrity": "sha512-7Lr60wLlYcGG+VDnnOY9xpn8Zz3yyJcWGSjNEbXPEGaaD0nTZLNZ1nIXRhTeYZwosK5GvPDFxq68kdLxczskHA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -983,41 +482,42 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.14" + "@angular/core": "21.0.5" } }, "node_modules/@angular/build": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/build/-/build-20.3.12.tgz", - "integrity": "sha512-iAZve4VPviC8y6RFctyh3qFXSlP5mth9K46/0zasB4LV4pcmu8BrzIHERxIn/jCDNdVdPh973kxo1ksO4WpyuA==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-21.0.3.tgz", + "integrity": "sha512-3h2s0Igruei1RB/Hmu7nwbKvjJQ2ykNaiicXYuS2muWUBhDg+lm0QsGTGXrQV2BD0M9YdHU4Byh9upiZgMYpjA==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "0.2003.12", - "@babel/core": "7.28.3", + "@angular-devkit/architect": "0.2100.3", + "@babel/core": "7.28.4", "@babel/helper-annotate-as-pure": "7.27.3", "@babel/helper-split-export-declaration": "7.24.7", - "@inquirer/confirm": "5.1.14", + "@inquirer/confirm": "5.1.19", "@vitejs/plugin-basic-ssl": "2.1.0", "beasties": "0.3.5", - "browserslist": "^4.23.0", - "esbuild": "0.25.9", + "browserslist": "^4.26.0", + "esbuild": "0.26.0", "https-proxy-agent": "7.0.6", "istanbul-lib-instrument": "6.0.3", "jsonc-parser": "3.3.1", - "listr2": "9.0.1", - "magic-string": "0.30.17", + "listr2": "9.0.5", + "magic-string": "0.30.19", "mrmime": "2.0.1", "parse5-html-rewriting-stream": "8.0.0", "picomatch": "4.0.3", "piscina": "5.1.3", - "rollup": "4.52.3", - "sass": "1.90.0", - "semver": "7.7.2", + "rolldown": "1.0.0-beta.47", + "sass": "1.93.2", + "semver": "7.7.3", "source-map-support": "0.5.21", - "tinyglobby": "0.2.14", - "vite": "7.1.11", + "tinyglobby": "0.2.15", + "undici": "7.16.0", + "vite": "7.2.2", "watchpack": "2.4.4" }, "engines": { @@ -1026,25 +526,25 @@ "yarn": ">= 1.13.0" }, "optionalDependencies": { - "lmdb": "3.4.2" + "lmdb": "3.4.3" }, "peerDependencies": { - "@angular/compiler": "^20.0.0", - "@angular/compiler-cli": "^20.0.0", - "@angular/core": "^20.0.0", - "@angular/localize": "^20.0.0", - "@angular/platform-browser": "^20.0.0", - "@angular/platform-server": "^20.0.0", - "@angular/service-worker": "^20.0.0", - "@angular/ssr": "^20.3.12", + "@angular/compiler": "^21.0.0", + "@angular/compiler-cli": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/localize": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/platform-server": "^21.0.0", + "@angular/service-worker": "^21.0.0", + "@angular/ssr": "^21.0.3", "karma": "^6.4.0", "less": "^4.2.0", - "ng-packagr": "^20.0.0", + "ng-packagr": "^21.0.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0", "tslib": "^2.3.0", - "typescript": ">=5.8 <6.0", - "vitest": "^3.1.1" + "typescript": ">=5.9 <6.0", + "vitest": "^4.0.8" }, "peerDependenciesMeta": { "@angular/core": { @@ -1085,145 +585,47 @@ } } }, - "node_modules/@angular/build/node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@angular/build/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@angular/build/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/build/node_modules/sass": { - "version": "1.90.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz", - "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^4.0.0", - "immutable": "^5.0.2", - "source-map-js": ">=0.6.2 <2.0.0" - }, - "bin": { - "sass": "sass.js" - }, - "engines": { - "node": ">=14.0.0" - }, - "optionalDependencies": { - "@parcel/watcher": "^2.4.1" - } - }, - "node_modules/@angular/build/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@angular/build/node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, "node_modules/@angular/cdk": { - "version": "20.2.14", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-20.2.14.tgz", - "integrity": "sha512-7bZxc01URbiPiIBWThQ69XwOxVduqEKN4PhpbF2AAyfMc/W8Hcr4VoIJOwL0O1Nkq5beS8pCAqoOeIgFyXd/kg==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-21.0.3.tgz", + "integrity": "sha512-abfckeZfFvovdpxuQHRE4gS1VLNa05Dx0ZSKLGVL9DsQsi4pgn6wWg1y9TkXMlmtpG/EhLmCBxUc6LOHfdeWQA==", "license": "MIT", "dependencies": { "parse5": "^8.0.0", "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/common": "^20.0.0 || ^21.0.0", - "@angular/core": "^20.0.0 || ^21.0.0", + "@angular/common": "^21.0.0 || ^22.0.0", + "@angular/core": "^21.0.0 || ^22.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/cli": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-20.3.12.tgz", - "integrity": "sha512-vqVyVjbFPCRMjA5evL7tV2JeR6Anuzb9WcXTMB17fr7uzKNNAvo7KyRaOJjp+TU4JDARTNyGPy0aywfPx7R60A==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-21.0.3.tgz", + "integrity": "sha512-3lMR3J231JhLgAt37yEULSHFte3zPeta9VYpIIf92JiBsTnWrvKnaK8RXhfdiSQrvhqQ9FMQdl5AG62r1c4dbA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.2003.12", - "@angular-devkit/core": "20.3.12", - "@angular-devkit/schematics": "20.3.12", - "@inquirer/prompts": "7.8.2", - "@listr2/prompt-adapter-inquirer": "3.0.1", - "@modelcontextprotocol/sdk": "1.17.3", - "@schematics/angular": "20.3.12", + "@angular-devkit/architect": "0.2100.3", + "@angular-devkit/core": "21.0.3", + "@angular-devkit/schematics": "21.0.3", + "@inquirer/prompts": "7.9.0", + "@listr2/prompt-adapter-inquirer": "3.0.5", + "@modelcontextprotocol/sdk": "1.24.0", + "@schematics/angular": "21.0.3", "@yarnpkg/lockfile": "1.1.0", - "algoliasearch": "5.35.0", + "algoliasearch": "5.40.1", "ini": "5.0.0", "jsonc-parser": "3.3.1", - "listr2": "9.0.1", - "npm-package-arg": "13.0.0", - "pacote": "21.0.0", - "resolve": "1.22.10", - "semver": "7.7.2", + "listr2": "9.0.5", + "npm-package-arg": "13.0.1", + "pacote": "21.0.3", + "parse5-html-rewriting-stream": "8.0.0", + "resolve": "1.22.11", + "semver": "7.7.3", "yargs": "18.0.0", - "zod": "3.25.76" + "zod": "4.1.13" }, "bin": { "ng": "bin/ng.js" @@ -1234,44 +636,10 @@ "yarn": ">= 1.13.0" } }, - "node_modules/@angular/cli/node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular/common": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.14.tgz", - "integrity": "sha512-OOUvjTtnpktJLsNupA+GFT2q5zNocPdpOENA8aSrXvAheNybLjgi+otO3U3sQsvB1VwaoEZ9GT5O3lZlstnA/A==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-21.0.5.tgz", + "integrity": "sha512-/ZI11F6Wxr8TZRVO4O7pmhBJ9YxDg9mvA76e0PiivmqZggM02HY0y3XPMP3hAOe4K+PfaVBgMAu3P9t32klzfA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1280,14 +648,14 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/core": "20.3.14", + "@angular/core": "21.0.5", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.14.tgz", - "integrity": "sha512-KFbfPPAbclzGDujCVruflCD9j4Zwwxvrg7Y4C9GJYs3LZ85t+BfIMDDnvpBUM07ZLnfY4TO4gQdHmJAcaGGXDQ==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.0.5.tgz", + "integrity": "sha512-92sv9pVm9o/8KfPM7T8j5VQmTaSOqmIajrJF8evXE2dNJcwkBpVtzZUqDzr23AV3vg94C7eYU64i8qrsmJ+cYQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1297,13 +665,13 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.14.tgz", - "integrity": "sha512-lFg9ikwRClzDPjdFiwynbVFIi1RJZf/0i+OHa3Ns2gzXxJeHNKMJrHHjWZ2DU4N2UpxH0YAPe22N9Bie28IuQQ==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-21.0.5.tgz", + "integrity": "sha512-45sFKqt+badXl6Ab2XsxuOsdi0BbIZgcc9TdwmFPdXMNfcSUYDcPiOA0l1iPwDIZiu4VyqzepMfnHB9IwCatgA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "7.28.3", + "@babel/core": "7.28.4", "@jridgewell/sourcemap-codec": "^1.4.14", "chokidar": "^4.0.0", "convert-source-map": "^1.5.1", @@ -1320,8 +688,8 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.14", - "typescript": ">=5.8 <6.0" + "@angular/compiler": "21.0.5", + "typescript": ">=5.9 <6.0" }, "peerDependenciesMeta": { "typescript": { @@ -1329,58 +697,10 @@ } } }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", - "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.3", - "@babel/parser": "^7.28.3", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@angular/core": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.14.tgz", - "integrity": "sha512-rpyEbhWF6Fj/xI9IvNLZh5QBUYnoXuF7vX54CCtyQ2MHALxRR/aa1WRxjRM96cF2OqodQ/Gj3oYW8ei8hlBh4w==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-21.0.5.tgz", + "integrity": "sha512-HFXfO5YsBVM+IEaU8h3DZSxO98yDZM2v49NlSVNDzFD3fhnkpTmcgT2NKz9ulIiuV9N376itt+x+NG12sg/+Fw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1389,9 +709,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/compiler": "20.3.14", + "@angular/compiler": "21.0.5", "rxjs": "^6.5.3 || ^7.4.0", - "zone.js": "~0.15.0" + "zone.js": "~0.15.0 || ~0.16.0" }, "peerDependenciesMeta": { "@angular/compiler": { @@ -1403,9 +723,9 @@ } }, "node_modules/@angular/forms": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.14.tgz", - "integrity": "sha512-fGrJ589tU+AKoxf+kaRrEw7wlSfVr1/z/Fz625ggFCc6ySQEityKW3JsnLfNkh5qGrdxib4BOfF78f9J7Pyk+w==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-21.0.5.tgz", + "integrity": "sha512-RcmXs/LgKyc7D70xVT+3aK/H2SCFEyuebAiw72Iz1te1Gbql2GDFF6hgEOaNwOUglDg8ogN5MdVif2DbRLD3Hw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1414,16 +734,17 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14", + "@angular/common": "21.0.5", + "@angular/core": "21.0.5", + "@angular/platform-browser": "21.0.5", + "@standard-schema/spec": "^1.0.0", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/platform-browser": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.14.tgz", - "integrity": "sha512-Lviz9GfsIyOIBDal8QhIBKU8OMH29A0RhFw2opTC50sqKadXLN9CD7iSaAwQbNLc4mc3JAF4zth0AzKdHLbz7Q==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-21.0.5.tgz", + "integrity": "sha512-UVCrqOxFmX6kAG3Y6jqjCWvLoTP7fxeY96AsxTMp1fkBdqbQbEPleWQpwngNimsuUPvf+rA6XOxsqiDmRex5mA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1432,9 +753,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/animations": "20.3.14", - "@angular/common": "20.3.14", - "@angular/core": "20.3.14" + "@angular/animations": "21.0.5", + "@angular/common": "21.0.5", + "@angular/core": "21.0.5" }, "peerDependenciesMeta": { "@angular/animations": { @@ -1443,9 +764,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.14.tgz", - "integrity": "sha512-g9z/g8gIOrBCX1SQ/GWwB0+JXBC6CKe0+yRyy9GGeBLm/YXWZHxTkmnDmueXXfPtUl8TOAInE22wlLcfunWTrg==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-21.0.5.tgz", + "integrity": "sha512-0P5vFSS6UhiU7IBeVqPEKmRhMtyQqyXGN9+zF7kLK8H0cx1j0eGVmHRsVuY2YKoVp97fXDIeVGSbO0t5ZcFhoA==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1454,16 +775,16 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/compiler": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14" + "@angular/common": "21.0.5", + "@angular/compiler": "21.0.5", + "@angular/core": "21.0.5", + "@angular/platform-browser": "21.0.5" } }, "node_modules/@angular/router": { - "version": "20.3.14", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.14.tgz", - "integrity": "sha512-gi7/NuHRS9n9RCwh03VuVFizVMa2lKL/s+7yP3Ecq2nQ5uSeTMWb/91OmGEBwncI3wKPkYdQ9g3n6PvK/O8uDQ==", + "version": "21.0.5", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-21.0.5.tgz", + "integrity": "sha512-IFmf0Wd7jSOoZ8TI+4RXMsYmnIfHQG+kGxeMQVKrefTdr3uEHW/TEsNzbW5bkCpVJHRm4EhkH4hSu8D8tUQffQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -1472,9 +793,9 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { - "@angular/common": "20.3.14", - "@angular/core": "20.3.14", - "@angular/platform-browser": "20.3.14", + "@angular/common": "21.0.5", + "@angular/core": "21.0.5", + "@angular/platform-browser": "21.0.5", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -1608,83 +929,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -1695,20 +939,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", @@ -1741,79 +971,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", @@ -1857,21 +1014,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helpers": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", @@ -1902,1159 +1044,6 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", - "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", - "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.0", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.3", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.3", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", - "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -3130,20 +1119,44 @@ "node": ">=0.1.90" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", - "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "node_modules/@emnapi/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "dev": true, "license": "MIT", - "engines": { - "node": ">=14.17.0" + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.26.0.tgz", + "integrity": "sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA==", "cpu": [ "ppc64" ], @@ -3158,9 +1171,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.26.0.tgz", + "integrity": "sha512-C0hkDsYNHZkBtPxxDx177JN90/1MiCpvBNjz1f5yWJo1+5+c5zr8apjastpEG+wtPjo9FFtGG7owSsAxyKiHxA==", "cpu": [ "arm" ], @@ -3175,9 +1188,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.26.0.tgz", + "integrity": "sha512-DDnoJ5eoa13L8zPh87PUlRd/IyFaIKOlRbxiwcSbeumcJ7UZKdtuMCHa1Q27LWQggug6W4m28i4/O2qiQQ5NZQ==", "cpu": [ "arm64" ], @@ -3192,9 +1205,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.26.0.tgz", + "integrity": "sha512-bKDkGXGZnj0T70cRpgmv549x38Vr2O3UWLbjT2qmIkdIWcmlg8yebcFWoT9Dku7b5OV3UqPEuNKRzlNhjwUJ9A==", "cpu": [ "x64" ], @@ -3209,9 +1222,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.26.0.tgz", + "integrity": "sha512-6Z3naJgOuAIB0RLlJkYc81An3rTlQ/IeRdrU3dOea8h/PvZSgitZV+thNuIccw0MuK1GmIAnAmd5TrMZad8FTQ==", "cpu": [ "arm64" ], @@ -3226,9 +1239,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.26.0.tgz", + "integrity": "sha512-OPnYj0zpYW0tHusMefyaMvNYQX5pNQuSsHFTHUBNp3vVXupwqpxofcjVsUx11CQhGVkGeXjC3WLjh91hgBG2xw==", "cpu": [ "x64" ], @@ -3243,9 +1256,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.26.0.tgz", + "integrity": "sha512-jix2fa6GQeZhO1sCKNaNMjfj5hbOvoL2F5t+w6gEPxALumkpOV/wq7oUBMHBn2hY2dOm+mEV/K+xfZy3mrsxNQ==", "cpu": [ "arm64" ], @@ -3260,9 +1273,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.26.0.tgz", + "integrity": "sha512-tccJaH5xHJD/239LjbVvJwf6T4kSzbk6wPFerF0uwWlkw/u7HL+wnAzAH5GB2irGhYemDgiNTp8wJzhAHQ64oA==", "cpu": [ "x64" ], @@ -3277,9 +1290,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.26.0.tgz", + "integrity": "sha512-JY8NyU31SyRmRpuc5W8PQarAx4TvuYbyxbPIpHAZdr/0g4iBr8KwQBS4kiiamGl2f42BBecHusYCsyxi7Kn8UQ==", "cpu": [ "arm" ], @@ -3294,9 +1307,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.26.0.tgz", + "integrity": "sha512-IMJYN7FSkLttYyTbsbme0Ra14cBO5z47kpamo16IwggzzATFY2lcZAwkbcNkWiAduKrTgFJP7fW5cBI7FzcuNQ==", "cpu": [ "arm64" ], @@ -3311,9 +1324,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.26.0.tgz", + "integrity": "sha512-XITaGqGVLgk8WOHw8We9Z1L0lbLFip8LyQzKYFKO4zFo1PFaaSKsbNjvkb7O8kEXytmSGRkYpE8LLVpPJpsSlw==", "cpu": [ "ia32" ], @@ -3328,9 +1341,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.26.0.tgz", + "integrity": "sha512-MkggfbDIczStUJwq9wU7gQ7kO33d8j9lWuOCDifN9t47+PeI+9m2QVh51EI/zZQ1spZtFMC1nzBJ+qNGCjJnsg==", "cpu": [ "loong64" ], @@ -3345,9 +1358,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.26.0.tgz", + "integrity": "sha512-fUYup12HZWAeccNLhQ5HwNBPr4zXCPgUWzEq2Rfw7UwqwfQrFZ0SR/JljaURR8xIh9t+o1lNUFTECUTmaP7yKA==", "cpu": [ "mips64el" ], @@ -3362,9 +1375,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.26.0.tgz", + "integrity": "sha512-MzRKhM0Ip+//VYwC8tialCiwUQ4G65WfALtJEFyU0GKJzfTYoPBw5XNWf0SLbCUYQbxTKamlVwPmcw4DgZzFxg==", "cpu": [ "ppc64" ], @@ -3379,9 +1392,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.26.0.tgz", + "integrity": "sha512-QhCc32CwI1I4Jrg1enCv292sm3YJprW8WHHlyxJhae/dVs+KRWkbvz2Nynl5HmZDW/m9ZxrXayHzjzVNvQMGQA==", "cpu": [ "riscv64" ], @@ -3396,9 +1409,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.26.0.tgz", + "integrity": "sha512-1D6vi6lfI18aNT1aTf2HV+RIlm6fxtlAp8eOJ4mmnbYmZ4boz8zYDar86sIYNh0wmiLJEbW/EocaKAX6Yso2fw==", "cpu": [ "s390x" ], @@ -3413,9 +1426,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.26.0.tgz", + "integrity": "sha512-rnDcepj7LjrKFvZkx+WrBv6wECeYACcFjdNPvVPojCPJD8nHpb3pv3AuR9CXgdnjH1O23btICj0rsp0L9wAnHA==", "cpu": [ "x64" ], @@ -3430,9 +1443,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.26.0.tgz", + "integrity": "sha512-FSWmgGp0mDNjEXXFcsf12BmVrb+sZBBBlyh3LwB/B9ac3Kkc8x5D2WimYW9N7SUkolui8JzVnVlWh7ZmjCpnxw==", "cpu": [ "arm64" ], @@ -3447,9 +1460,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.26.0.tgz", + "integrity": "sha512-0QfciUDFryD39QoSPUDshj4uNEjQhp73+3pbSAaxjV2qGOEDsM67P7KbJq7LzHoVl46oqhIhJ1S+skKGR7lMXA==", "cpu": [ "x64" ], @@ -3464,9 +1477,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.26.0.tgz", + "integrity": "sha512-vmAK+nHhIZWImwJ3RNw9hX3fU4UGN/OqbSE0imqljNbUQC3GvVJ1jpwYoTfD6mmXmQaxdJY6Hn4jQbLGJKg5Yw==", "cpu": [ "arm64" ], @@ -3481,9 +1494,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.26.0.tgz", + "integrity": "sha512-GPXF7RMkJ7o9bTyUsnyNtrFMqgM3X+uM/LWw4CeHIjqc32fm0Ir6jKDnWHpj8xHFstgWDUYseSABK9KCkHGnpg==", "cpu": [ "x64" ], @@ -3498,9 +1511,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.26.0.tgz", + "integrity": "sha512-nUHZ5jEYqbBthbiBksbmHTlbb5eElyVfs/s1iHQ8rLBq1eWsd5maOnDpCocw1OM8kFK747d1Xms8dXJHtduxSw==", "cpu": [ "arm64" ], @@ -3515,9 +1528,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.26.0.tgz", + "integrity": "sha512-TMg3KCTCYYaVO+R6P5mSORhcNDDlemUVnUbb8QkboUtOhb5JWKAzd5uMIMECJQOxHZ/R+N8HHtDF5ylzLfMiLw==", "cpu": [ "x64" ], @@ -3532,9 +1545,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.26.0.tgz", + "integrity": "sha512-apqYgoAUd6ZCb9Phcs8zN32q6l0ZQzQBdVXOofa6WvHDlSOhwCWgSfVQabGViThS40Y1NA4SCvQickgZMFZRlA==", "cpu": [ "arm64" ], @@ -3549,9 +1562,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.26.0.tgz", + "integrity": "sha512-FGJAcImbJNZzLWu7U6WB0iKHl4RuY4TsXEwxJPl9UZLS47agIZuILZEX3Pagfw7I4J3ddflomt9f0apfaJSbaw==", "cpu": [ "ia32" ], @@ -3566,9 +1579,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.26.0.tgz", + "integrity": "sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A==", "cpu": [ "x64" ], @@ -3759,9 +1772,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { @@ -3895,14 +1908,14 @@ } }, "node_modules/@inquirer/confirm": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.14.tgz", - "integrity": "sha512-5yR4IBfe0kXe59r1YCTG8WXkUbl7Z35HK87Sw+WUyGD8wNUx7JvY7laahzeytyE1oLn74bQnL7hstctQxisQ8Q==", + "version": "5.1.19", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.19.tgz", + "integrity": "sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/core": "^10.1.15", - "@inquirer/type": "^3.0.8" + "@inquirer/core": "^10.3.0", + "@inquirer/type": "^3.0.9" }, "engines": { "node": ">=18" @@ -4090,22 +2103,22 @@ } }, "node_modules/@inquirer/prompts": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.8.2.tgz", - "integrity": "sha512-nqhDw2ZcAUrKNPwhjinJny903bRhI0rQhiDz1LksjeRxqa36i3l75+4iXbOy0rlDpLJGxqtgoPavQjmmyS5UJw==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.9.0.tgz", + "integrity": "sha512-X7/+dG9SLpSzRkwgG5/xiIzW0oMrV3C0HOa7YHG1WnrLK+vCQHfte4k/T80059YBdei29RBC3s+pSMvPJDU9/A==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.2.1", - "@inquirer/confirm": "^5.1.14", - "@inquirer/editor": "^4.2.17", - "@inquirer/expand": "^4.0.17", - "@inquirer/input": "^4.2.1", - "@inquirer/number": "^3.0.17", - "@inquirer/password": "^4.0.17", - "@inquirer/rawlist": "^4.1.5", - "@inquirer/search": "^3.1.0", - "@inquirer/select": "^4.3.1" + "@inquirer/checkbox": "^4.3.0", + "@inquirer/confirm": "^5.1.19", + "@inquirer/editor": "^4.2.21", + "@inquirer/expand": "^4.0.21", + "@inquirer/input": "^4.2.5", + "@inquirer/number": "^3.0.21", + "@inquirer/password": "^4.0.21", + "@inquirer/rawlist": "^4.1.9", + "@inquirer/search": "^3.2.0", + "@inquirer/select": "^4.4.0" }, "engines": { "node": ">=18" @@ -4232,80 +2245,6 @@ "node": "20 || >=22" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "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/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "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, - "license": "MIT", - "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/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "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/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -4365,6 +2304,8 @@ "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -4386,160 +2327,33 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jsonjoy.com/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/buffers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", - "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/codegen": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", - "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/json-pack": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", - "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/base64": "^1.1.2", - "@jsonjoy.com/buffers": "^1.2.0", - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/json-pointer": "^1.0.2", - "@jsonjoy.com/util": "^1.9.0", - "hyperdyperid": "^1.2.0", - "thingies": "^2.5.0", - "tree-dump": "^1.1.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/json-pointer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", - "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/util": "^1.9.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/util": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", - "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/buffers": "^1.0.0", - "@jsonjoy.com/codegen": "^1.0.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, "node_modules/@kurkle/color": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", "license": "MIT" }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "dev": true, - "license": "MIT" - }, "node_modules/@listr2/prompt-adapter-inquirer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.1.tgz", - "integrity": "sha512-3XFmGwm3u6ioREG+ynAQB7FoxfajgQnMhIu8wC5eo/Lsih4aKDg0VuIMGaOsYn7hJSJagSeaD4K8yfpkEoDEmA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@listr2/prompt-adapter-inquirer/-/prompt-adapter-inquirer-3.0.5.tgz", + "integrity": "sha512-WELs+hj6xcilkloBXYf9XXK8tYEnKsgLj01Xl5ONUJpKjmT5hGVUzNUS5tooUxs7pGMrw+jFD/41WpqW4V3LDA==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/type": "^3.0.7" + "@inquirer/type": "^3.0.8" }, "engines": { "node": ">=20.0.0" }, "peerDependencies": { "@inquirer/prompts": ">= 3 < 8", - "listr2": "9.0.1" + "listr2": "9.0.5" } }, "node_modules/@lmdb/lmdb-darwin-arm64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.2.tgz", - "integrity": "sha512-NK80WwDoODyPaSazKbzd3NEJ3ygePrkERilZshxBViBARNz21rmediktGHExoj9n5t9+ChlgLlxecdFKLCuCKg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.3.tgz", + "integrity": "sha512-zR6Y45VNtW5s+A+4AyhrJk0VJKhXdkLhrySCpCu7PSdnakebsOzNxf58p5Xoq66vOSuueGAxlqDAF49HwdrSTQ==", "cpu": [ "arm64" ], @@ -4551,9 +2365,9 @@ ] }, "node_modules/@lmdb/lmdb-darwin-x64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.2.tgz", - "integrity": "sha512-zevaowQNmrp3U7Fz1s9pls5aIgpKRsKb3dZWDINtLiozh3jZI9fBrI19lYYBxqdyiIyNdlyiidPnwPShj4aK+w==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.3.tgz", + "integrity": "sha512-nfGm5pQksBGfaj9uMbjC0YyQreny/Pl7mIDtHtw6g7WQuCgeLullr9FNRsYyKplaEJBPrCVpEjpAznxTBIrXBw==", "cpu": [ "x64" ], @@ -4565,9 +2379,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.2.tgz", - "integrity": "sha512-OmHCULY17rkx/RoCoXlzU7LyR8xqrksgdYWwtYa14l/sseezZ8seKWXcogHcjulBddER5NnEFV4L/Jtr2nyxeg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.3.tgz", + "integrity": "sha512-Kjqomp7i0rgSbYSUmv9JnXpS55zYT/YcW3Bdf9oqOTjcH0/8tFAP8MLhu/i9V2pMKIURDZk63Ww49DTK0T3c/Q==", "cpu": [ "arm" ], @@ -4579,9 +2393,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-arm64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.2.tgz", - "integrity": "sha512-ZBEfbNZdkneebvZs98Lq30jMY8V9IJzckVeigGivV7nTHJc+89Ctomp1kAIWKlwIG0ovCDrFI448GzFPORANYg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.3.tgz", + "integrity": "sha512-uX9eaPqWb740wg5D3TCvU/js23lSRSKT7lJrrQ8IuEG/VLgpPlxO3lHDywU44yFYdGS7pElBn6ioKFKhvALZlw==", "cpu": [ "arm64" ], @@ -4593,9 +2407,9 @@ ] }, "node_modules/@lmdb/lmdb-linux-x64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.2.tgz", - "integrity": "sha512-vL9nM17C77lohPYE4YaAQvfZCSVJSryE4fXdi8M7uWPBnU+9DJabgKVAeyDb84ZM2vcFseoBE4/AagVtJeRE7g==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.4.3.tgz", + "integrity": "sha512-7/8l20D55CfwdMupkc3fNxNJdn4bHsti2X0cp6PwiXlLeSFvAfWs5kCCx+2Cyje4l4GtN//LtKWjTru/9hDJQg==", "cpu": [ "x64" ], @@ -4607,9 +2421,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-arm64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.2.tgz", - "integrity": "sha512-SXWjdBfNDze4ZPeLtYIzsIeDJDJ/SdsA0pEXcUBayUIMO0FQBHfVZZyHXQjjHr4cvOAzANBgIiqaXRwfMhzmLw==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.3.tgz", + "integrity": "sha512-yWVR0e5Gl35EGJBsAuqPOdjtUYuN8CcTLKrqpQFoM+KsMadViVCulhKNhkcjSGJB88Am5bRPjMro4MBB9FS23Q==", "cpu": [ "arm64" ], @@ -4621,9 +2435,9 @@ ] }, "node_modules/@lmdb/lmdb-win32-x64": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.2.tgz", - "integrity": "sha512-IY+r3bxKW6Q6sIPiMC0L533DEfRJSXibjSI3Ft/w9Q8KQBNqEIvUFXt+09wV8S5BRk0a8uSF19YWxuRwEfI90g==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.3.tgz", + "integrity": "sha512-1JdBkcO0Vrua4LUgr4jAe4FUyluwCeq/pDkBrlaVjX3/BBWP1TzVjCL+TibWNQtPAL1BITXPAhlK5Ru4FBd/hg==", "cpu": [ "x64" ], @@ -4635,13 +2449,14 @@ ] }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.3.tgz", - "integrity": "sha512-JPwUKWSsbzx+DLFznf/QZ32Qa+ptfbUlHhRLrBQBAFu9iI1iYvizM4p+zhhRDceSsPutXp4z+R/HPVphlIiclg==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.0.tgz", + "integrity": "sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.6", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", @@ -4649,39 +2464,28 @@ "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.24.1" + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" }, "engines": { "node": ">=18" - } - }, - "node_modules/@modelcontextprotocol/sdk/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "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" + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", @@ -5089,21 +2893,17 @@ "node": ">= 10" } }, - "node_modules/@ngtools/webpack": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-20.3.12.tgz", - "integrity": "sha512-ePuofHOtbgvEq2t+hcmL30s4q9HQ/nv9ABwpLiELdVIObcWUnrnizAvM7hujve/9CQL6gRCeEkxPLPS4ZrK9AQ==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", + "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", "dev": true, "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^20.0.0", - "typescript": ">=5.8 <6.0", - "webpack": "^5.54.0" + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" } }, "node_modules/@nodelib/fs.scandir": { @@ -5142,60 +2942,86 @@ } }, "node_modules/@npmcli/agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", - "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", + "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "dev": true, "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", - "lru-cache": "^10.0.1", + "lru-cache": "^11.2.1", "socks-proxy-agent": "^8.0.3" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/@npmcli/fs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", - "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", + "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-6.0.3.tgz", - "integrity": "sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.1.tgz", + "integrity": "sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/promise-spawn": "^8.0.0", - "ini": "^5.0.0", - "lru-cache": "^10.0.1", - "npm-pick-manifest": "^10.0.0", - "proc-log": "^5.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "ini": "^6.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", "semver": "^7.3.5", - "which": "^5.0.0" + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/git/node_modules/ini": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/git/node_modules/isexe": { @@ -5209,16 +3035,29 @@ } }, "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@npmcli/git/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/@npmcli/git/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, "license": "ISC", "dependencies": { @@ -5228,7 +3067,7 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/installed-package-contents": { @@ -5249,74 +3088,77 @@ } }, "node_modules/@npmcli/node-gyp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-4.0.0.tgz", - "integrity": "sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", + "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/package-json": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-6.2.0.tgz", - "integrity": "sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.4.tgz", + "integrity": "sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", - "glob": "^10.2.2", - "hosted-git-info": "^8.0.0", - "json-parse-even-better-errors": "^4.0.0", - "proc-log": "^5.0.0", + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", "semver": "^7.5.3", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/package-json/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.1.1", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/@npmcli/package-json/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@npmcli/package-json/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "node_modules/@npmcli/package-json/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, "node_modules/@npmcli/promise-spawn": { "version": "8.0.3", @@ -5358,31 +3200,44 @@ } }, "node_modules/@npmcli/redact": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", - "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", + "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/run-script": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", - "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.3.tgz", + "integrity": "sha512-ER2N6itRkzWbbtVmZ9WKaWxVlKlOeBFF1/7xx+KA5J1xKa4JjUwBdb6tDpk0v1qA+d+VDwHI9qmLcXSWcmi+Rw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", + "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^6.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@npmcli/run-script/node_modules/isexe": { @@ -5395,10 +3250,20 @@ "node": ">=16" } }, + "node_modules/@npmcli/run-script/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/@npmcli/run-script/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, "license": "ISC", "dependencies": { @@ -5408,7 +3273,17 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.96.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.96.0.tgz", + "integrity": "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@parcel/watcher": { @@ -5461,7 +3336,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5483,7 +3357,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5505,7 +3378,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5527,7 +3399,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5549,7 +3420,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5571,7 +3441,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5593,7 +3462,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5615,7 +3483,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5637,7 +3504,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5659,7 +3525,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5681,7 +3546,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5703,7 +3567,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5725,7 +3588,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -5756,25 +3618,26 @@ "license": "MIT", "optional": true }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@primeng/themes": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/@primeng/themes/-/themes-20.4.0.tgz", - "integrity": "sha512-bh1yIRbCDAo+OLhQ+bm8sgwlZFRphwlR3/GXOdshJVurm5/Up+CWzoRqsZw/Q2RSrq0x3rDNA2pOTIYpcwgXbA==", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/@primeng/themes/-/themes-21.0.2.tgz", + "integrity": "sha512-wSw8BtrNUZBmgU5umzMj9WQKVyrLRQkAYORnRYI4wD6+TXVb/J2kQLHCA26vHbXfI6pO4fruft9mySFWlzXk0Q==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { "@primeuix/styled": "^0.7.4", - "@primeuix/themes": "^1.2.5" + "@primeuix/themes": "^2.0.2" + } + }, + "node_modules/@primeuix/motion": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/@primeuix/motion/-/motion-0.0.10.tgz", + "integrity": "sha512-PsZwOPq79Scp7/ionshRcQ5xKVf9+zuLcyY5mf6onK8chHT5C9JGphmcIZ4CzcqxuGEpsm8AIbTGy+zS3RtzLA==", + "license": "MIT", + "dependencies": { + "@primeuix/utils": "^0.6.3" + }, + "engines": { + "node": ">=12.11.0" } }, "node_modules/@primeuix/styled": { @@ -5790,21 +3653,21 @@ } }, "node_modules/@primeuix/styles": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-1.2.5.tgz", - "integrity": "sha512-nypFRct/oaaBZqP4jinT0puW8ZIfs4u+l/vqUFmJEPU332fl5ePj6DoOpQgTLzo3OfmvSmz5a5/5b4OJJmmi7Q==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@primeuix/styles/-/styles-2.0.2.tgz", + "integrity": "sha512-LNtkJsTonNHF5ag+9s3+zQzm00+LRmffw68QRIHy6S/dam1JpdrrAnUzNYlWbaY7aE2EkZvQmx7Np7+PyHn+ow==", "license": "MIT", "dependencies": { - "@primeuix/styled": "^0.7.3" + "@primeuix/styled": "^0.7.4" } }, "node_modules/@primeuix/themes": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@primeuix/themes/-/themes-1.2.5.tgz", - "integrity": "sha512-n3YkwJrHQaEESc/D/A/iD815sxp8cKnmzscA6a8Tm8YvMtYU32eCahwLLe6h5rywghVwxASWuG36XBgISYOIjQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@primeuix/themes/-/themes-2.0.2.tgz", + "integrity": "sha512-prwQvA3tDGBz8yWSUenaJUttEMCEvPvxwOfFhDPmSe1vwsfVKL2Nmh5eZvtPFQnxmIOPsHZS7zc0/L3CzJ83Eg==", "license": "MIT", "dependencies": { - "@primeuix/styled": "^0.7.3" + "@primeuix/styled": "^0.7.4" } }, "node_modules/@primeuix/utils": { @@ -5816,10 +3679,255 @@ "node": ">=12.11.0" } }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.47.tgz", + "integrity": "sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.47.tgz", + "integrity": "sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.47.tgz", + "integrity": "sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.47.tgz", + "integrity": "sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.47.tgz", + "integrity": "sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.47.tgz", + "integrity": "sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.47.tgz", + "integrity": "sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.47.tgz", + "integrity": "sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.47.tgz", + "integrity": "sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.47.tgz", + "integrity": "sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.47.tgz", + "integrity": "sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.47.tgz", + "integrity": "sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.47.tgz", + "integrity": "sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.47.tgz", + "integrity": "sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", + "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", - "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz", + "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==", "cpu": [ "arm" ], @@ -5831,9 +3939,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", - "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz", + "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==", "cpu": [ "arm64" ], @@ -5845,9 +3953,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", - "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz", + "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==", "cpu": [ "arm64" ], @@ -5859,9 +3967,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", - "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz", + "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==", "cpu": [ "x64" ], @@ -5873,9 +3981,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", - "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz", + "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==", "cpu": [ "arm64" ], @@ -5887,9 +3995,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", - "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz", + "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==", "cpu": [ "x64" ], @@ -5901,9 +4009,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", - "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz", + "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==", "cpu": [ "arm" ], @@ -5915,9 +4023,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", - "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz", + "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==", "cpu": [ "arm" ], @@ -5929,9 +4037,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", - "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz", + "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==", "cpu": [ "arm64" ], @@ -5943,9 +4051,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", - "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz", + "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==", "cpu": [ "arm64" ], @@ -5957,9 +4065,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", - "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz", + "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==", "cpu": [ "loong64" ], @@ -5971,9 +4079,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", - "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz", + "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==", "cpu": [ "ppc64" ], @@ -5985,9 +4093,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", - "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz", + "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==", "cpu": [ "riscv64" ], @@ -5999,9 +4107,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", - "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz", + "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==", "cpu": [ "riscv64" ], @@ -6013,9 +4121,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", - "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz", + "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==", "cpu": [ "s390x" ], @@ -6027,9 +4135,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", - "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz", + "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==", "cpu": [ "x64" ], @@ -6041,9 +4149,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", - "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz", + "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==", "cpu": [ "x64" ], @@ -6055,9 +4163,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", - "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz", + "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==", "cpu": [ "arm64" ], @@ -6069,9 +4177,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", - "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz", + "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==", "cpu": [ "arm64" ], @@ -6083,9 +4191,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", - "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz", + "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==", "cpu": [ "ia32" ], @@ -6097,9 +4205,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", - "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz", + "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==", "cpu": [ "x64" ], @@ -6111,9 +4219,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", - "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz", + "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==", "cpu": [ "x64" ], @@ -6125,14 +4233,14 @@ ] }, "node_modules/@schematics/angular": { - "version": "20.3.12", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-20.3.12.tgz", - "integrity": "sha512-ikl+nkWUab/Z4eSkBHgq9FLIUH8qh4OcYKeBQ0fyWqIUFHyjjK0JOfwmH1g/3zAmuUMtkthHCehAtyKzCTQjVA==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-21.0.3.tgz", + "integrity": "sha512-XYOI2WOz8B+ydJ8iUHRXrUyjTx+YGdCQ8b2FlXnU46ksIctVU+zt4Zgu6462xeaPwOFYw6+r+TvaBAZ14a82Gw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.3.12", - "@angular-devkit/schematics": "20.3.12", + "@angular-devkit/core": "21.0.3", + "@angular-devkit/schematics": "21.0.3", "jsonc-parser": "3.3.1" }, "engines": { @@ -6142,32 +4250,32 @@ } }, "node_modules/@sigstore/bundle": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", - "integrity": "sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", + "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.0" + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/core": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-2.0.0.tgz", - "integrity": "sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.0.0.tgz", + "integrity": "sha512-NgbJ+aW9gQl/25+GIEGYcCyi8M+ng2/5X04BMuIgoDfgvp18vDcoNHOQjQsG9418HGNYRxG3vfEXaR1ayD37gg==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/protobuf-specs": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.4.3.tgz", - "integrity": "sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", + "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -6175,50 +4283,50 @@ } }, "node_modules/@sigstore/sign": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-3.1.0.tgz", - "integrity": "sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.0.1.tgz", + "integrity": "sha512-KFNGy01gx9Y3IBPG/CergxR9RZpN43N+lt3EozEfeoyqm8vEiLxwRl3ZO5sPx3Obv1ix/p7FWOlPc2Jgwfp9PA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "make-fetch-happen": "^14.0.2", + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^15.0.2", "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/tuf": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-3.1.1.tgz", - "integrity": "sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.0.tgz", + "integrity": "sha512-0QFuWDHOQmz7t66gfpfNO6aEjoFrdhkJaej/AOqb4kqWZVbPWFZifXZzkxyQBB1OwTbkhdT3LNpMFxwkTvf+2w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.4.1", - "tuf-js": "^3.0.1" + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^4.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/verify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-2.1.1.tgz", - "integrity": "sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.0.0.tgz", + "integrity": "sha512-moXtHH33AobOhTZF8xcX1MpOFqdvfCk7v6+teJL8zymBiDXwEsQH6XG9HGx2VIxnJZNm4cNSzflTLDnQLmIdmw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.1" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@socket.io/component-emitter": { @@ -6228,6 +4336,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT", + "peer": true + }, "node_modules/@stomp/rx-stomp": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@stomp/rx-stomp/-/rx-stomp-2.3.0.tgz", @@ -6269,9 +4384,9 @@ } }, "node_modules/@tufjs/models": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-3.0.1.tgz", - "integrity": "sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.0.0.tgz", + "integrity": "sha512-h5x5ga/hh82COe+GoD4+gKUeV4T3iaYOxqLt41GRKApinPI7DMidhCmNVTjKfhCWFJIGXaFJee07XczdT4jdZQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6279,7 +4394,7 @@ "minimatch": "^9.0.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@tweenjs/tween.js": { @@ -6288,46 +4403,15 @@ "integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==", "license": "MIT" }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" + "tslib": "^2.4.0" } }, "node_modules/@types/cors": { @@ -6340,28 +4424,6 @@ "@types/node": "*" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -6369,49 +4431,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", - "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.17", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", - "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/jasmine": { "version": "5.1.13", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.13.tgz", @@ -6436,13 +4455,6 @@ "localforage": "*" } }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "24.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", @@ -6453,80 +4465,6 @@ "undici-types": "~7.16.0" } }, - "node_modules/@types/node-forge": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", - "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, "node_modules/@types/showdown": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz", @@ -6534,39 +4472,18 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", - "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz", + "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.48.0", - "@typescript-eslint/type-utils": "8.48.0", - "@typescript-eslint/utils": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0", - "graphemer": "^1.4.0", + "@typescript-eslint/scope-manager": "8.50.0", + "@typescript-eslint/type-utils": "8.50.0", + "@typescript-eslint/utils": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" @@ -6579,22 +4496,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.48.0", + "@typescript-eslint/parser": "^8.50.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz", - "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz", + "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.48.0", - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0", + "@typescript-eslint/scope-manager": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4" }, "engines": { @@ -6610,14 +4527,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", - "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", + "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.48.0", - "@typescript-eslint/types": "^8.48.0", + "@typescript-eslint/tsconfig-utils": "^8.50.0", + "@typescript-eslint/types": "^8.50.0", "debug": "^4.3.4" }, "engines": { @@ -6632,14 +4549,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", - "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz", + "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0" + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6650,9 +4567,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", - "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", + "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", "dev": true, "license": "MIT", "engines": { @@ -6667,15 +4584,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz", - "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz", + "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0", - "@typescript-eslint/utils": "8.48.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.0", + "@typescript-eslint/utils": "8.50.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -6692,9 +4609,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", - "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", + "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", "dev": true, "license": "MIT", "engines": { @@ -6706,16 +4623,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", - "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", + "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.48.0", - "@typescript-eslint/tsconfig-utils": "8.48.0", - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/visitor-keys": "8.48.0", + "@typescript-eslint/project-service": "8.50.0", + "@typescript-eslint/tsconfig-utils": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -6734,16 +4651,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", - "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz", + "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.48.0", - "@typescript-eslint/types": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0" + "@typescript-eslint/scope-manager": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6758,13 +4675,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", - "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", + "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/types": "8.50.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -6801,167 +4718,6 @@ "vite": "^6.0.0 || ^7.0.0" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, "node_modules/@xmldom/xmldom": { "version": "0.7.13", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.13.tgz", @@ -6972,20 +4728,6 @@ "node": ">=10.0.0" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/@yarnpkg/lockfile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", @@ -6994,13 +4736,13 @@ "license": "BSD-2-Clause" }, "node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", "dev": true, "license": "ISC", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/accepts": { @@ -7030,19 +4772,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -7053,35 +4782,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/adjust-sourcemap-loader": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", - "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "regex-parser": "^2.2.11" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -7127,63 +4827,51 @@ } } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, "node_modules/algoliasearch": { - "version": "5.35.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.35.0.tgz", - "integrity": "sha512-Y+moNhsqgLmvJdgTsO4GZNgsaDWv8AOGAaPeIeHKlDn/XunoAqYbA+XNpBd1dW8GOXAUDyxC9Rxc7AV4kpFcIg==", + "version": "5.40.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.40.1.tgz", + "integrity": "sha512-iUNxcXUNg9085TJx0HJLjqtDE0r1RZ0GOGrt8KNQqQT5ugu8lZsHuMUYW/e0lHhq6xBvmktU9Bw4CXP9VQeKrg==", "dev": true, "license": "MIT", "dependencies": { - "@algolia/abtesting": "1.1.0", - "@algolia/client-abtesting": "5.35.0", - "@algolia/client-analytics": "5.35.0", - "@algolia/client-common": "5.35.0", - "@algolia/client-insights": "5.35.0", - "@algolia/client-personalization": "5.35.0", - "@algolia/client-query-suggestions": "5.35.0", - "@algolia/client-search": "5.35.0", - "@algolia/ingestion": "1.35.0", - "@algolia/monitoring": "1.35.0", - "@algolia/recommend": "5.35.0", - "@algolia/requester-browser-xhr": "5.35.0", - "@algolia/requester-fetch": "5.35.0", - "@algolia/requester-node-http": "5.35.0" + "@algolia/abtesting": "1.6.1", + "@algolia/client-abtesting": "5.40.1", + "@algolia/client-analytics": "5.40.1", + "@algolia/client-common": "5.40.1", + "@algolia/client-insights": "5.40.1", + "@algolia/client-personalization": "5.40.1", + "@algolia/client-query-suggestions": "5.40.1", + "@algolia/client-search": "5.40.1", + "@algolia/ingestion": "1.40.1", + "@algolia/monitoring": "1.40.1", + "@algolia/recommend": "5.40.1", + "@algolia/requester-browser-xhr": "5.40.1", + "@algolia/requester-fetch": "5.40.1", + "@algolia/requester-node-http": "5.40.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/angular-eslint": { - "version": "20.7.0", - "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-20.7.0.tgz", - "integrity": "sha512-BCiTCLO3dr8pGPaM7qLcCruWNcoNNHnLn4DPqE5tHk1TAnTx5TcGy0p/FygharZw5RjWfDHLBjFfpeh4XWLMmQ==", + "version": "21.1.0", + "resolved": "https://registry.npmjs.org/angular-eslint/-/angular-eslint-21.1.0.tgz", + "integrity": "sha512-qXpIEBNYpfgpBaFblnyFegVSQjWCVUdCXTHvMcvtNtmMgtPwIDKvG8wuJo5BbQ/MNt2d8npmnRUaS2ddzdCzww==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": ">= 20.0.0 < 21.0.0", - "@angular-devkit/schematics": ">= 20.0.0 < 21.0.0", - "@angular-eslint/builder": "20.7.0", - "@angular-eslint/eslint-plugin": "20.7.0", - "@angular-eslint/eslint-plugin-template": "20.7.0", - "@angular-eslint/schematics": "20.7.0", - "@angular-eslint/template-parser": "20.7.0", + "@angular-devkit/core": ">= 21.0.0 < 22.0.0", + "@angular-devkit/schematics": ">= 21.0.0 < 22.0.0", + "@angular-eslint/builder": "21.1.0", + "@angular-eslint/eslint-plugin": "21.1.0", + "@angular-eslint/eslint-plugin-template": "21.1.0", + "@angular-eslint/schematics": "21.1.0", + "@angular-eslint/template-parser": "21.1.0", "@typescript-eslint/types": "^8.0.0", "@typescript-eslint/utils": "^8.0.0" }, "peerDependencies": { + "@angular/cli": ">= 21.0.0 < 22.0.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": "*", "typescript-eslint": "^8.0.0" @@ -7202,16 +4890,6 @@ "@angular/core": ">=20.0.0" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-escapes": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", @@ -7228,19 +4906,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -7324,17 +4989,10 @@ "node": ">= 0.4" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "license": "MIT" - }, "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", "dev": true, "funding": [ { @@ -7352,10 +5010,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", - "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -7379,75 +5036,6 @@ "node": ">= 0.4" } }, - "node_modules/babel-loader": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-10.0.0.tgz", - "integrity": "sha512-z8jt+EdS61AMw22nSfoNJAZ0vrtmhPRVi6ghL3rCeRZI8cdNYFiV5xeV3HbE7rlZZNmGH8BVccwWt8/ED0QOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": "^18.20.0 || ^20.10.0 || >=22.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5.61.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -7466,22 +5054,15 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.8.32", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", - "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", + "version": "2.9.8", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.8.tgz", + "integrity": "sha512-Y1fOuNDowLfgKOypdc9SPABfoWXuZHBOyCS4cD52IeZBhr4Md6CLLs6atcxVrzRmQ06E7hSlm5bHHApPKR/byA==", "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true, - "license": "MIT" - }, "node_modules/beasties": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/beasties/-/beasties-0.3.5.tgz", @@ -7502,16 +5083,6 @@ "node": ">=14.0.0" } }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -7549,17 +5120,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -7590,9 +5150,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -7610,11 +5170,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -7630,22 +5190,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -7657,92 +5201,83 @@ } }, "node_modules/cacache": { - "version": "19.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", - "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", + "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/fs": "^4.0.0", + "@npmcli/fs": "^5.0.0", "fs-minipass": "^3.0.0", - "glob": "^10.2.2", - "lru-cache": "^10.0.1", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", - "ssri": "^12.0.0", - "tar": "^7.4.3", - "unique-filename": "^4.0.0" + "ssri": "^13.0.0", + "unique-filename": "^5.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/cacache/node_modules/glob": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", + "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", + "minimatch": "^10.1.1", "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "path-scurry": "^2.0.0" }, - "bin": { - "glob": "dist/esm/bin.mjs" + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/cacache/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, - "node_modules/cacache/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "node_modules/cacache/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">=18" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "node_modules/cacache/node_modules/ssri": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/call-bind-apply-helpers": { @@ -7796,9 +5331,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "version": "1.0.30001760", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", + "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", "dev": true, "funding": [ { @@ -7887,23 +5422,13 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=10" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" + "node": ">=18" } }, "node_modules/cli-cursor": { @@ -7923,30 +5448,47 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.3.0.tgz", + "integrity": "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8008,34 +5550,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -8072,86 +5586,6 @@ "node": "^12.20.0 || >=14" } }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "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/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -8175,16 +5609,6 @@ "node": ">= 0.10.0" } }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/connect/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -8311,6 +5735,8 @@ "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "is-what": "^3.14.1" }, @@ -8318,30 +5744,6 @@ "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/copy-webpack-plugin": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.1.tgz", - "integrity": "sha512-J+YV3WfhY6W/Xf9h+J1znYuqTye2xkBUIGyTPWuBAT27qajBa5mR4f8WBmfDY3YjRftT2kqZZiLi1qf0H+UOFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-parent": "^6.0.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2", - "tinyglobby": "^0.2.12" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, "node_modules/core-js": { "version": "3.47.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", @@ -8353,20 +5755,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-js-compat": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", - "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -8387,33 +5775,6 @@ "node": ">= 0.10" } }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -8429,42 +5790,6 @@ "node": ">= 8" } }, - "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", - "dev": true, - "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.27.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, "node_modules/css-select": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-6.0.0.tgz", @@ -8562,49 +5887,6 @@ "dev": true, "license": "MIT" }, - "node_modules/default-browser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", - "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -8637,13 +5919,6 @@ "node": ">=8" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "license": "MIT" - }, "node_modules/di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -8663,19 +5938,6 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -8763,13 +6025,6 @@ "node": ">= 0.4" } }, - "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, - "license": "MIT" - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8778,9 +6033,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.262", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", - "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, @@ -8791,16 +6046,6 @@ "dev": true, "license": "MIT" }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -8954,20 +6199,6 @@ } } }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/ent": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.2.tgz", @@ -9051,6 +6282,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "prr": "~1.0.1" }, @@ -9058,16 +6290,6 @@ "errno": "cli.js" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -9088,13 +6310,6 @@ "node": ">= 0.4" } }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -9149,9 +6364,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.26.0.tgz", + "integrity": "sha512-3Hq7jri+tRrVWha+ZeIVhl4qJRha/XjRNSopvTsOaCvfPHrflTYTcUFcEjMKdxofsXXsdc4zjg5NOTnL4Gl57Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -9162,45 +6377,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" - } - }, - "node_modules/esbuild-wasm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.25.9.tgz", - "integrity": "sha512-Jpv5tCSwQg18aCqCRD3oHIX/prBhXMDapIoG//A+6+dV0e7KQMGFg85ihJ5T1EeMjbZjON3TqFy0VrGAnIHLDA==", - "dev": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" + "@esbuild/aix-ppc64": "0.26.0", + "@esbuild/android-arm": "0.26.0", + "@esbuild/android-arm64": "0.26.0", + "@esbuild/android-x64": "0.26.0", + "@esbuild/darwin-arm64": "0.26.0", + "@esbuild/darwin-x64": "0.26.0", + "@esbuild/freebsd-arm64": "0.26.0", + "@esbuild/freebsd-x64": "0.26.0", + "@esbuild/linux-arm": "0.26.0", + "@esbuild/linux-arm64": "0.26.0", + "@esbuild/linux-ia32": "0.26.0", + "@esbuild/linux-loong64": "0.26.0", + "@esbuild/linux-mips64el": "0.26.0", + "@esbuild/linux-ppc64": "0.26.0", + "@esbuild/linux-riscv64": "0.26.0", + "@esbuild/linux-s390x": "0.26.0", + "@esbuild/linux-x64": "0.26.0", + "@esbuild/netbsd-arm64": "0.26.0", + "@esbuild/netbsd-x64": "0.26.0", + "@esbuild/openbsd-arm64": "0.26.0", + "@esbuild/openbsd-x64": "0.26.0", + "@esbuild/openharmony-arm64": "0.26.0", + "@esbuild/sunos-x64": "0.26.0", + "@esbuild/win32-arm64": "0.26.0", + "@esbuild/win32-ia32": "0.26.0", + "@esbuild/win32-x64": "0.26.0" } }, "node_modules/escalade": { @@ -9234,9 +6436,9 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "dependencies": { @@ -9246,7 +6448,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/js": "9.39.2", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -9530,16 +6732,6 @@ "dev": true, "license": "MIT" }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -9571,19 +6763,20 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -9726,19 +6919,6 @@ "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -9782,9 +6962,9 @@ } }, "node_modules/finalhandler": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", - "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "license": "MIT", "dependencies": { @@ -9796,7 +6976,11 @@ "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/find-up": { @@ -9816,16 +7000,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -9868,23 +7042,6 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -10083,23 +7240,6 @@ "node": ">=10.13.0" } }, - "node_modules/glob-to-regex.js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", - "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -10164,20 +7304,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -10243,28 +7369,15 @@ } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -10312,13 +7425,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true, - "license": "MIT" - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -10340,13 +7446,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "dev": true, - "license": "MIT" - }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", @@ -10376,24 +7475,6 @@ "node": ">= 14" } }, - "node_modules/http-proxy-middleware": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz", - "integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.15", - "debug": "^4.3.6", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.3", - "is-plain-object": "^5.0.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -10408,20 +7489,10 @@ "node": ">= 14" } }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.18" - } - }, "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", "dev": true, "license": "MIT", "dependencies": { @@ -10435,19 +7506,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/ignore": { "version": "7.0.5", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", @@ -10494,6 +7552,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "bin": { "image-size": "bin/image-size.js" }, @@ -10589,13 +7648,6 @@ "node": ">= 0.10" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -10623,22 +7675,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -10649,13 +7685,16 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -10673,25 +7712,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -10705,19 +7725,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -10727,29 +7734,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -10794,23 +7778,9 @@ "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", "dev": true, - "license": "MIT" - }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dev": true, "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "optional": true, + "peer": true }, "node_modules/isarray": { "version": "1.0.0", @@ -10838,16 +7808,6 @@ "dev": true, "license": "ISC" }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -10929,60 +7889,13 @@ "node": ">=8" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/jasmine-core": { - "version": "5.12.1", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.12.1.tgz", - "integrity": "sha512-P/UbRZ0LKwXe7wEpwDheuhunPwITn4oPALhrJEQJo6756EwNGnsK/TSQrWojBB4cQDQ+VaxWYws9tFNDuiMh2Q==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.13.0.tgz", + "integrity": "sha512-vsYjfh7lyqvZX5QgqKc4YH8phs7g96Z8bsdIFNEU3VqXhlHaq+vov/Fgn/sr6MiUczdZkyXRC3TX369Ll4Nzbw==", "dev": true, "license": "MIT" }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -10994,6 +7907,16 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11035,13 +7958,13 @@ "license": "MIT" }, "node_modules/json-parse-even-better-errors": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", - "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", + "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "dev": true, "license": "MIT", "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/json-schema-traverse": { @@ -11285,16 +8208,6 @@ "dev": true, "license": "MIT" }, - "node_modules/karma-source-map-support": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", - "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "source-map-support": "^0.5.5" - } - }, "node_modules/karma/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -11678,33 +8591,13 @@ "json-buffer": "3.0.1" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" - } - }, "node_modules/less": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/less/-/less-4.4.2.tgz", "integrity": "sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==", "dev": true, "license": "Apache-2.0", + "optional": true, "peer": true, "dependencies": { "copy-anything": "^2.0.1", @@ -11727,33 +8620,6 @@ "source-map": "~0.6.0" } }, - "node_modules/less-loader": { - "version": "12.3.0", - "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.3.0.tgz", - "integrity": "sha512-0M6+uYulvYIWs52y0LqN4+QM9TqWAohYSNTo4htE8Z7Cn3G/qQMEmktfHmyJT23k+20kU9zHH2wrfFXkxNLtVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "less": "^3.5.0 || ^4.0.0", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, "node_modules/less/node_modules/make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", @@ -11835,24 +8701,6 @@ "node": ">= 0.8.0" } }, - "node_modules/license-webpack-plugin": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", - "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", - "dev": true, - "license": "ISC", - "dependencies": { - "webpack-sources": "^3.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-sources": { - "optional": true - } - } - }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -11881,13 +8729,13 @@ "license": "MIT" }, "node_modules/listr2": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.1.tgz", - "integrity": "sha512-SL0JY3DaxylDuo/MecFeiC+7pedM0zia33zl0vcjgwcq1q1FWWF1To9EIauPbl8GbMCU0R2e0uJ8bZunhYKD2g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", "dependencies": { - "cli-truncate": "^4.0.0", + "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", @@ -11937,9 +8785,9 @@ } }, "node_modules/lmdb": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.2.tgz", - "integrity": "sha512-nwVGUfTBUwJKXd6lRV8pFNfnrCC1+l49ESJRM19t/tFb/97QfJEixe5DYRvug5JO7DSFKoKaVy7oGMt5rVqZvg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.4.3.tgz", + "integrity": "sha512-GWV1kVi6uhrXWqe+3NXWO73OYe8fto6q8JMo0HOpk1vf8nEyFWgo4CSNJpIFzsOxOrysVUlcO48qRbQfmKd1gA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -11955,37 +8803,13 @@ "download-lmdb-prebuilds": "bin/download-prebuilds.js" }, "optionalDependencies": { - "@lmdb/lmdb-darwin-arm64": "3.4.2", - "@lmdb/lmdb-darwin-x64": "3.4.2", - "@lmdb/lmdb-linux-arm": "3.4.2", - "@lmdb/lmdb-linux-arm64": "3.4.2", - "@lmdb/lmdb-linux-x64": "3.4.2", - "@lmdb/lmdb-win32-arm64": "3.4.2", - "@lmdb/lmdb-win32-x64": "3.4.2" - } - }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/loader-utils": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.3.1.tgz", - "integrity": "sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.13.0" + "@lmdb/lmdb-darwin-arm64": "3.4.3", + "@lmdb/lmdb-darwin-x64": "3.4.3", + "@lmdb/lmdb-linux-arm": "3.4.3", + "@lmdb/lmdb-linux-arm64": "3.4.3", + "@lmdb/lmdb-linux-x64": "3.4.3", + "@lmdb/lmdb-win32-arm64": "3.4.3", + "@lmdb/lmdb-win32-x64": "3.4.3" } }, "node_modules/localforage": { @@ -12040,13 +8864,6 @@ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "license": "MIT" }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -12062,14 +8879,14 @@ "license": "MIT" }, "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" }, "engines": { "node": ">=18" @@ -12078,32 +8895,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", @@ -12137,39 +8928,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/log-update/node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -12216,13 +8974,13 @@ } }, "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/make-dir": { @@ -12242,26 +9000,49 @@ } }, "node_modules/make-fetch-happen": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", - "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.3.tgz", + "integrity": "sha512-iyyEpDty1mwW3dGlYXAJqC/azFn5PPvgKVwXayOGBSmKLxhKZ9fg4qIan2ePpp1vJIwfFiO34LAPZgq9SZW9Aw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/agent": "^3.0.0", - "cacache": "^19.0.1", + "@npmcli/agent": "^4.0.0", + "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", - "proc-log": "^5.0.0", + "proc-log": "^6.0.0", "promise-retry": "^2.0.1", - "ssri": "^12.0.0" + "ssri": "^13.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.0.tgz", + "integrity": "sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/marks-pane": { @@ -12290,25 +9071,6 @@ "node": ">= 0.8" } }, - "node_modules/memfs": { - "version": "4.51.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.51.0.tgz", - "integrity": "sha512-4zngfkVM/GpIhC8YazOsM6E8hoB33NP0BCESPOA6z7qaL6umPJNqkO8CNYaLV2FB2MV6H1O3x2luHHOSqppv+A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/json-pack": "^1.11.0", - "@jsonjoy.com/util": "^1.9.0", - "glob-to-regex.js": "^1.0.1", - "thingies": "^2.5.0", - "tree-dump": "^1.0.3", - "tslib": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - } - }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -12322,13 +9084,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -12338,16 +9093,6 @@ "node": ">= 8" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -12426,34 +9171,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", - "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "license": "ISC" - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -12504,9 +9221,9 @@ } }, "node_modules/minipass-fetch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", - "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.0.tgz", + "integrity": "sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==", "dev": true, "license": "MIT", "dependencies": { @@ -12515,7 +9232,7 @@ "minizlib": "^3.0.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" }, "optionalDependencies": { "encoding": "^0.1.13" @@ -12664,9 +9381,9 @@ "license": "MIT" }, "node_modules/msgpackr": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz", - "integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==", + "version": "1.11.7", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.7.tgz", + "integrity": "sha512-LXTGyT6FlVaL090rEEz4ZwfifKEj+tF4FOoaC6cEbHxvIDu8qc3QPXmLHKQO+XSOmvhbXZOCYPhFG4VCBZyJfQ==", "dev": true, "license": "MIT", "optional": true, @@ -12697,20 +9414,6 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" } }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dev": true, - "license": "MIT", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, "node_modules/mute-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", @@ -12764,6 +9467,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "iconv-lite": "^0.6.3", "sax": "^1.2.4" @@ -12782,6 +9486,7 @@ "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -12799,13 +9504,6 @@ "node": ">= 0.6" } }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, "node_modules/next-tick": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", @@ -12845,9 +9543,9 @@ } }, "node_modules/ngx-extended-pdf-viewer": { - "version": "25.6.1", - "resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-25.6.1.tgz", - "integrity": "sha512-NZ2msuI9tRi9yVvTEBqsdleYfsiEa5FNrZdhNvM0NXlh9VoZNH+MRiGo5SQPyoVCRi3iQX5PhBcboQBGPDlxMg==", + "version": "25.6.4", + "resolved": "https://registry.npmjs.org/ngx-extended-pdf-viewer/-/ngx-extended-pdf-viewer-25.6.4.tgz", + "integrity": "sha512-eYIiWzatcupB7HKDtcOOZN7gcLFjqAkeIAlZOMIO6XyUJnTe+PUZLZGit/19mtO/8fAaH41lMyyh8MAcU8NAhA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.3.0" @@ -12858,16 +9556,16 @@ } }, "node_modules/ngx-infinite-scroll": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-20.0.0.tgz", - "integrity": "sha512-Mh7lg85jeDPzZirxPHjyMagCcC3vbk+yPO5uoXHNkmGer8MrO6vOydOX306qY4QYDyMJ+ngIQgpl5HJXfc590A==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-21.0.0.tgz", + "integrity": "sha512-bm1hCB7aoO9zyiNZBBOLHx9t+cwk/tLiFG5eQB8pWiNup6I2eQiHoT5B2gqlJjj4GfuER5phy5AIWl/B7YiwSQ==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/common": ">=20.0.0 <21.0.0", - "@angular/core": ">=20.0.0 <21.0.0" + "@angular/common": ">=21.0.0 <22.0.0", + "@angular/core": ">=21.0.0 <22.0.0" } }, "node_modules/node-addon-api": { @@ -12878,39 +9576,29 @@ "license": "MIT", "optional": true }, - "node_modules/node-forge": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", - "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/node-gyp": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", - "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.1.0.tgz", + "integrity": "sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==", "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", - "make-fetch-happen": "^14.0.3", - "nopt": "^8.0.0", - "proc-log": "^5.0.0", + "make-fetch-happen": "^15.0.0", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.4.3", + "tar": "^7.5.2", "tinyglobby": "^0.2.12", - "which": "^5.0.0" + "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp-build-optional-packages": { @@ -12929,16 +9617,6 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/node-gyp/node_modules/isexe": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", @@ -12949,27 +9627,20 @@ "node": ">=16" } }, - "node_modules/node-gyp/node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "node_modules/node-gyp/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, + "license": "ISC", "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-gyp/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.0.tgz", + "integrity": "sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==", "dev": true, "license": "ISC", "dependencies": { @@ -12979,17 +9650,7 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/node-releases": { @@ -13000,19 +9661,19 @@ "license": "MIT" }, "node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", "dev": true, "license": "ISC", "dependencies": { - "abbrev": "^3.0.0" + "abbrev": "^4.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/normalize-path": { @@ -13024,16 +9685,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npm-bundled": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-4.0.0.tgz", @@ -13048,16 +9699,16 @@ } }, "node_modules/npm-install-checks": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-7.1.2.tgz", - "integrity": "sha512-z9HJBCYw9Zr8BqXcllKIs5nI+QggAImbBdHphOzVYrz2CB4iQ6FzWyKmlqDZua+51nAu7FcemlbTc9VgQN5XDQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", + "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-normalize-package-bin": { @@ -13071,9 +9722,9 @@ } }, "node_modules/npm-package-arg": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.0.tgz", - "integrity": "sha512-+t2etZAGcB7TbbLHfDwooV9ppB2LhhcT6A+L9cahsf9mEUAoQ6CktLEVvEnpD0N5CkX7zJqnPGaFtoQDy9EkHQ==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.1.tgz", + "integrity": "sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==", "dev": true, "license": "ISC", "dependencies": { @@ -13111,111 +9762,59 @@ } }, "node_modules/npm-pick-manifest": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-10.0.0.tgz", - "integrity": "sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", + "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "dev": true, "license": "ISC", "dependencies": { - "npm-install-checks": "^7.1.0", - "npm-normalize-package-bin": "^4.0.0", - "npm-package-arg": "^12.0.0", + "npm-install-checks": "^8.0.0", + "npm-normalize-package-bin": "^5.0.0", + "npm-package-arg": "^13.0.0", "semver": "^7.3.5" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-pick-manifest/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", + "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", "dev": true, "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-pick-manifest/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/npm-pick-manifest/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/npm-registry-fetch": { - "version": "18.0.2", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-18.0.2.tgz", - "integrity": "sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==", + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", + "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/redact": "^3.0.0", + "@npmcli/redact": "^4.0.0", "jsonparse": "^1.3.1", - "make-fetch-happen": "^14.0.0", + "make-fetch-happen": "^15.0.0", "minipass": "^7.0.2", - "minipass-fetch": "^4.0.0", + "minipass-fetch": "^5.0.0", "minizlib": "^3.0.1", - "npm-package-arg": "^12.0.0", - "proc-log": "^5.0.0" + "npm-package-arg": "^13.0.0", + "proc-log": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-registry-fetch/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", + "node_modules/npm-registry-fetch/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", "dev": true, "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/npm-registry-fetch/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/npm-registry-fetch/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/nth-check": { @@ -13262,13 +9861,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true, - "license": "MIT" - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -13282,16 +9874,6 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -13318,25 +9900,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -13356,24 +9919,24 @@ } }, "node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.0.0.tgz", + "integrity": "sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.3.0", + "chalk": "^5.6.2", "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", + "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" + "string-width": "^8.1.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -13392,6 +9955,23 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/ora/node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ordered-binary": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", @@ -13445,65 +10025,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-retry": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/pacote": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.0.tgz", - "integrity": "sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==", + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.0.3.tgz", + "integrity": "sha512-itdFlanxO0nmQv4ORsvA9K1wv40IPfB9OmWqfaJWvoJ30VKyHsqNgDVeG+TVhI7Gk7XW8slUy7cA9r6dF5qohw==", "dev": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^6.0.0", + "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^3.0.0", - "@npmcli/package-json": "^6.0.0", + "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^8.0.0", - "@npmcli/run-script": "^9.0.0", - "cacache": "^19.0.0", + "@npmcli/run-script": "^10.0.0", + "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", - "npm-package-arg": "^12.0.0", - "npm-packlist": "^10.0.0", - "npm-pick-manifest": "^10.0.0", - "npm-registry-fetch": "^18.0.0", + "npm-package-arg": "^13.0.0", + "npm-packlist": "^10.0.1", + "npm-pick-manifest": "^11.0.1", + "npm-registry-fetch": "^19.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", - "sigstore": "^3.0.0", + "sigstore": "^4.0.0", "ssri": "^12.0.0", - "tar": "^6.1.11" + "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" @@ -13512,42 +10057,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/pacote/node_modules/hosted-git-info": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.1.0.tgz", - "integrity": "sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/pacote/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/pacote/node_modules/npm-package-arg": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-12.0.2.tgz", - "integrity": "sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==", - "dev": true, - "license": "ISC", - "dependencies": { - "hosted-git-info": "^8.0.0", - "proc-log": "^5.0.0", - "semver": "^7.3.5", - "validate-npm-package-name": "^6.0.0" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -13573,38 +10082,14 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-json/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 0.10" } @@ -13721,28 +10206,31 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-to-regexp": { "version": "8.3.0", @@ -13932,48 +10420,6 @@ } } }, - "node_modules/postcss-loader": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", - "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "cosmiconfig": "^9.0.0", - "jiti": "^1.20.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/postcss-loader/node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "bin": { - "jiti": "bin/jiti.js" - } - }, "node_modules/postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -13981,97 +10427,6 @@ "dev": true, "license": "MIT" }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "dev": true, - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", @@ -14147,24 +10502,24 @@ "license": "MIT" }, "node_modules/primeng": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/primeng/-/primeng-20.4.0.tgz", - "integrity": "sha512-vXUD1G4/uet4rDkPW8xx7yZWj7RmsmexEJ3+GhpQgsNaLtPFsTCVfQq8v4FQ4tIs7shoD0hz76d3jtjGWZ49QQ==", + "version": "21.0.2", + "resolved": "https://registry.npmjs.org/primeng/-/primeng-21.0.2.tgz", + "integrity": "sha512-suf+xK3V3z2aG9OmQMAriqhMt79JM3jBSoRDu4XQKCiTiWDRnOvoVdCZCjByT32sE5tYqhrLBsCH0lT7RLcosg==", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { + "@primeuix/motion": "^0.0.10", "@primeuix/styled": "^0.7.4", - "@primeuix/styles": "^1.2.5", - "@primeuix/utils": "^0.6.2", + "@primeuix/styles": "^2.0.2", + "@primeuix/utils": "^0.6.3", "tslib": "^2.3.0" }, "peerDependencies": { - "@angular/animations": "^20.0.4", - "@angular/cdk": "^20.0.3", - "@angular/common": "^20.0.4", - "@angular/core": "^20.0.4", - "@angular/forms": "^20.0.4", - "@angular/platform-browser": "^20.0.4", - "@angular/router": "^20.0.4", + "@angular/cdk": "^21.0.0", + "@angular/common": "^21.0.0", + "@angular/core": "^21.0.0", + "@angular/forms": "^21.0.0", + "@angular/platform-browser": "^21.0.0", + "@angular/router": "^21.0.0", "rxjs": "^6.0.0 || ^7.8.1" } }, @@ -14218,7 +10573,8 @@ "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/punycode": { "version": "1.4.1", @@ -14308,16 +10664,6 @@ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -14389,71 +10735,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regex-parser": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.1.tgz", - "integrity": "sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -14511,48 +10792,6 @@ "node": ">=4" } }, - "node_modules/resolve-url-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", - "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "adjust-sourcemap-loader": "^4.0.0", - "convert-source-map": "^1.7.0", - "loader-utils": "^2.0.0", - "postcss": "^8.2.14", - "source-map": "0.6.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/resolve-url-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/resolve-url-loader/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -14614,10 +10853,43 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rolldown": { + "version": "1.0.0-beta.47", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.47.tgz", + "integrity": "sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.96.0", + "@rolldown/pluginutils": "1.0.0-beta.47" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.47", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.47", + "@rolldown/binding-darwin-x64": "1.0.0-beta.47", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.47", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.47", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.47", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.47", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.47", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.47", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.47", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.47", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.47", + "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.47", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.47" + } + }, "node_modules/rollup": { - "version": "4.52.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", - "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "version": "4.53.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz", + "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14631,28 +10903,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.3", - "@rollup/rollup-android-arm64": "4.52.3", - "@rollup/rollup-darwin-arm64": "4.52.3", - "@rollup/rollup-darwin-x64": "4.52.3", - "@rollup/rollup-freebsd-arm64": "4.52.3", - "@rollup/rollup-freebsd-x64": "4.52.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", - "@rollup/rollup-linux-arm-musleabihf": "4.52.3", - "@rollup/rollup-linux-arm64-gnu": "4.52.3", - "@rollup/rollup-linux-arm64-musl": "4.52.3", - "@rollup/rollup-linux-loong64-gnu": "4.52.3", - "@rollup/rollup-linux-ppc64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-gnu": "4.52.3", - "@rollup/rollup-linux-riscv64-musl": "4.52.3", - "@rollup/rollup-linux-s390x-gnu": "4.52.3", - "@rollup/rollup-linux-x64-gnu": "4.52.3", - "@rollup/rollup-linux-x64-musl": "4.52.3", - "@rollup/rollup-openharmony-arm64": "4.52.3", - "@rollup/rollup-win32-arm64-msvc": "4.52.3", - "@rollup/rollup-win32-ia32-msvc": "4.52.3", - "@rollup/rollup-win32-x64-gnu": "4.52.3", - "@rollup/rollup-win32-x64-msvc": "4.52.3", + "@rollup/rollup-android-arm-eabi": "4.53.5", + "@rollup/rollup-android-arm64": "4.53.5", + "@rollup/rollup-darwin-arm64": "4.53.5", + "@rollup/rollup-darwin-x64": "4.53.5", + "@rollup/rollup-freebsd-arm64": "4.53.5", + "@rollup/rollup-freebsd-x64": "4.53.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.5", + "@rollup/rollup-linux-arm-musleabihf": "4.53.5", + "@rollup/rollup-linux-arm64-gnu": "4.53.5", + "@rollup/rollup-linux-arm64-musl": "4.53.5", + "@rollup/rollup-linux-loong64-gnu": "4.53.5", + "@rollup/rollup-linux-ppc64-gnu": "4.53.5", + "@rollup/rollup-linux-riscv64-gnu": "4.53.5", + "@rollup/rollup-linux-riscv64-musl": "4.53.5", + "@rollup/rollup-linux-s390x-gnu": "4.53.5", + "@rollup/rollup-linux-x64-gnu": "4.53.5", + "@rollup/rollup-linux-x64-musl": "4.53.5", + "@rollup/rollup-openharmony-arm64": "4.53.5", + "@rollup/rollup-win32-arm64-msvc": "4.53.5", + "@rollup/rollup-win32-ia32-msvc": "4.53.5", + "@rollup/rollup-win32-x64-gnu": "4.53.5", + "@rollup/rollup-win32-x64-msvc": "4.53.5", "fsevents": "~2.3.2" } }, @@ -14673,19 +10945,6 @@ "node": ">= 18" } }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -14755,8 +11014,6 @@ "integrity": "sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==", "dev": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -14772,113 +11029,14 @@ "@parcel/watcher": "^2.4.1" } }, - "node_modules/sass-loader": { - "version": "16.0.5", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.5.tgz", - "integrity": "sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "neo-async": "^2.6.2" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", - "sass": "^1.3.0", - "sass-embedded": "*", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "node-sass": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, "node_modules/sax": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", "dev": true, "license": "BlueOak-1.0.0", - "optional": true - }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true, - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } + "optional": true, + "peer": true }, "node_modules/semver": { "version": "7.7.3", @@ -14894,175 +11052,36 @@ } }, "node_modules/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", - "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "mime-types": "^3.0.1", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", - "statuses": "^2.0.1" + "statuses": "^2.0.2" }, "engines": { "node": ">= 18" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-index/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", "dependencies": { @@ -15073,6 +11092,10 @@ }, "engines": { "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setimmediate": { @@ -15088,19 +11111,6 @@ "dev": true, "license": "ISC" }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15124,19 +11134,6 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/showdown": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", @@ -15243,35 +11240,35 @@ } }, "node_modules/sigstore": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-3.1.0.tgz", - "integrity": "sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.0.0.tgz", + "integrity": "sha512-Gw/FgHtrLM9WP8P5lLcSGh9OQcrTruWCELAiS48ik1QbL0cH+dfjomiRTUE9zzz+D1N6rOLkwXUvVmXZAsNE0Q==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^3.1.0", - "@sigstore/core": "^2.0.0", - "@sigstore/protobuf-specs": "^0.4.0", - "@sigstore/sign": "^3.1.0", - "@sigstore/tuf": "^3.1.0", - "@sigstore/verify": "^2.1.0" + "@sigstore/bundle": "^4.0.0", + "@sigstore/core": "^3.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^4.0.0", + "@sigstore/tuf": "^4.0.0", + "@sigstore/verify": "^3.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" @@ -15468,28 +11465,6 @@ "node": ">= 0.6" } }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/socks": { "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", @@ -15539,40 +11514,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-loader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", - "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.72.1" - } - }, - "node_modules/source-map-loader/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -15630,53 +11571,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/ssri": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", @@ -15755,62 +11649,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", @@ -15827,30 +11665,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -16048,120 +11862,32 @@ "node": ">=8.10.0" } }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tar/node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/terser": { "version": "5.44.0", @@ -16169,6 +11895,8 @@ "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", "dev": true, "license": "BSD-2-Clause", + "optional": true, + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -16182,47 +11910,14 @@ "node": ">=10" } }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/thenify": { "version": "3.3.1", @@ -16245,30 +11940,6 @@ "node": ">=0.8" } }, - "node_modules/thingies": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", - "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "^2" - } - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -16317,33 +11988,6 @@ "node": ">=0.6" } }, - "node_modules/tree-dump": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", - "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -16370,18 +12014,18 @@ "license": "0BSD" }, "node_modules/tuf-js": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-3.1.0.tgz", - "integrity": "sha512-3T3T04WzowbwV2FDiGXBbr81t64g1MUGGJRgT4x5o97N+8ArdhVCAF9IxFrxuSJmM3E5Asn7nKHkao0ibcZXAg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.0.0.tgz", + "integrity": "sha512-Lq7ieeGvXDXwpoSmOSgLWVdsGGV9J4a77oDTAPe/Ltrqnnm/ETaRlBAQTH5JatEh8KXuE6sddf9qAv1Q2282Hg==", "dev": true, "license": "MIT", "dependencies": { - "@tufjs/models": "3.0.1", + "@tufjs/models": "4.0.0", "debug": "^4.4.1", - "make-fetch-happen": "^14.0.3" + "make-fetch-happen": "^15.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/type": { @@ -16418,13 +12062,6 @@ "node": ">= 0.6" } }, - "node_modules/typed-assert": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", - "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", - "dev": true, - "license": "MIT" - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -16440,16 +12077,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.48.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz", - "integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz", + "integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.48.0", - "@typescript-eslint/parser": "8.48.0", - "@typescript-eslint/typescript-estree": "8.48.0", - "@typescript-eslint/utils": "8.48.0" + "@typescript-eslint/eslint-plugin": "8.50.0", + "@typescript-eslint/parser": "8.50.0", + "@typescript-eslint/typescript-estree": "8.50.0", + "@typescript-eslint/utils": "8.50.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -16490,6 +12127,16 @@ "node": "*" } }, + "node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -16497,74 +12144,30 @@ "dev": true, "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/unique-filename": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", - "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", + "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", "dev": true, "license": "ISC", "dependencies": { - "unique-slug": "^5.0.0" + "unique-slug": "^6.0.0" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/unique-slug": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", - "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", + "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/universalify": { @@ -16588,9 +12191,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -16700,9 +12303,9 @@ } }, "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz", + "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16774,6 +12377,490 @@ } } }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -16798,16 +12885,6 @@ "node": ">=10.13.0" } }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, "node_modules/weak-lru-cache": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", @@ -16816,790 +12893,6 @@ "license": "MIT", "optional": true }, - "node_modules/webpack": { - "version": "5.102.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", - "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.4", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz", - "integrity": "sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^4.6.0", - "mime-types": "^2.1.31", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", - "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.13", - "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.21", - "@types/express-serve-static-core": "^4.17.21", - "@types/serve-index": "^1.9.4", - "@types/serve-static": "^1.15.5", - "@types/sockjs": "^0.3.36", - "@types/ws": "^8.5.10", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.2.1", - "chokidar": "^3.6.0", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "express": "^4.21.2", - "graceful-fs": "^4.2.6", - "http-proxy-middleware": "^2.0.9", - "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", - "open": "^10.0.3", - "p-retry": "^6.2.0", - "schema-utils": "^4.2.0", - "selfsigned": "^2.4.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.4.2", - "ws": "^8.18.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/webpack-dev-server/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "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/webpack-dev-server/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/webpack-dev-server/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/webpack-dev-server/node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webpack-dev-server/node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/webpack-dev-server/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/ipaddr.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", - "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-dev-server/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-dev-server/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/webpack-dev-server/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/webpack-dev-server/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/webpack-dev-server/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/webpack-dev-server/node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webpack-dev-server/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/webpack-dev-server/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "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/webpack-dev-server/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/webpack-dev-server/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webpack-dev-server/node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/webpack-dev-server/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webpack-dev-server/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-subresource-integrity": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", - "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "typed-assert": "^1.0.8" - }, - "engines": { - "node": ">= 12" - }, - "peerDependencies": { - "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", - "webpack": "^5.12.0" - }, - "peerDependenciesMeta": { - "html-webpack-plugin": { - "optional": true - } - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpack/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -17616,13 +12909,6 @@ "node": ">= 8" } }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "license": "MIT" - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -17648,80 +12934,6 @@ "node": ">=8" } }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "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-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -17805,22 +13017,6 @@ } } }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -17879,6 +13075,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoctocolors-cjs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", @@ -17893,9 +13102,9 @@ } }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", "dev": true, "license": "MIT", "funding": { diff --git a/booklore-ui/package.json b/booklore-ui/package.json index 420a6810..5aa31fce 100644 --- a/booklore-ui/package.json +++ b/booklore-ui/package.json @@ -12,17 +12,17 @@ }, "private": true, "dependencies": { - "@angular/animations": "^20.3.5", - "@angular/cdk": "^20.2.9", - "@angular/common": "^20.3.5", - "@angular/compiler": "^20.3.5", - "@angular/core": "^20.3.5", - "@angular/forms": "^20.3.5", - "@angular/platform-browser": "^20.3.5", - "@angular/platform-browser-dynamic": "^20.3.5", - "@angular/router": "^20.3.5", + "@angular/animations": "^21.0.5", + "@angular/cdk": "^21.0.3", + "@angular/common": "^21.0.5", + "@angular/compiler": "^21.0.5", + "@angular/core": "^21.0.5", + "@angular/forms": "^21.0.5", + "@angular/platform-browser": "^21.0.5", + "@angular/platform-browser-dynamic": "^21.0.5", + "@angular/router": "^21.0.5", "@iharbeck/ngx-virtual-scroller": "^19.0.1", - "@primeng/themes": "^20.4.0", + "@primeng/themes": "^21.0.2", "@stomp/rx-stomp": "^2.3.0", "@stomp/stompjs": "^7.2.1", "@tweenjs/tween.js": "^25.0.0", @@ -34,10 +34,10 @@ "jwt-decode": "^4.0.0", "ng-lazyload-image": "^9.1.3", "ng2-charts": "^8.0.0", - "ngx-extended-pdf-viewer": "^25.6.1", - "ngx-infinite-scroll": "^20.0.0", + "ngx-extended-pdf-viewer": "^25.6.4", + "ngx-infinite-scroll": "^21.0.0", "primeicons": "^7.0.0", - "primeng": "^20.4.0", + "primeng": "^21.0.2", "quill": "^2.0.3", "rxjs": "^7.8.2", "showdown": "^2.1.0", @@ -47,16 +47,16 @@ "zone.js": "^0.16.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^20.3.5", - "@angular/cli": "^20.3.5", - "@angular/compiler-cli": "^20.3.5", + "@angular/build": "^21.0.3", + "@angular/cli": "^21.0.3", + "@angular/compiler-cli": "^21.0.5", "@tailwindcss/typography": "^0.5.19", "@types/jasmine": "^5.1.13", "@types/showdown": "^2.0.6", - "angular-eslint": "^20.3.5", - "autoprefixer": "^10.4.22", - "eslint": "^9.39.1", - "jasmine-core": "^5.12.1", + "angular-eslint": "^21.1.0", + "autoprefixer": "^10.4.23", + "eslint": "^9.39.2", + "jasmine-core": "^5.13.0", "karma": "^6.4.4", "karma-chrome-launcher": "^3.2.0", "karma-coverage": "^2.2.1", @@ -64,6 +64,6 @@ "karma-jasmine-html-reporter": "^2.1.0", "tailwindcss": "^3.4.17", "typescript": "~5.9.3", - "typescript-eslint": "^8.48.0" + "typescript-eslint": "^8.50.0" } -} +} \ No newline at end of file diff --git a/booklore-ui/src/app/features/book/components/additional-file-uploader/additional-file-uploader.component.ts b/booklore-ui/src/app/features/book/components/additional-file-uploader/additional-file-uploader.component.ts index 7d3180d7..17463f66 100644 --- a/booklore-ui/src/app/features/book/components/additional-file-uploader/additional-file-uploader.component.ts +++ b/booklore-ui/src/app/features/book/components/additional-file-uploader/additional-file-uploader.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, OnDestroy, ChangeDetectorRef } from '@angular/core'; -import { CommonModule } from '@angular/common'; + import { FormsModule } from '@angular/forms'; import { DynamicDialogRef, DynamicDialogConfig } from 'primeng/dynamicdialog'; import { Select } from 'primeng/select'; @@ -29,14 +29,13 @@ interface UploadingFile { selector: 'app-additional-file-uploader', standalone: true, imports: [ - CommonModule, FormsModule, Select, Button, FileUpload, Badge, Tooltip - ], +], templateUrl: './additional-file-uploader.component.html', styleUrls: ['./additional-file-uploader.component.scss'] }) diff --git a/booklore-ui/src/app/features/book/components/book-reviews/book-reviews.component.ts b/booklore-ui/src/app/features/book/components/book-reviews/book-reviews.component.ts index e8c33622..dbb5e0ae 100644 --- a/booklore-ui/src/app/features/book/components/book-reviews/book-reviews.component.ts +++ b/booklore-ui/src/app/features/book/components/book-reviews/book-reviews.component.ts @@ -1,5 +1,5 @@ import {Component, DestroyRef, inject, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core'; -import {CommonModule} from '@angular/common'; + import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {BookReview, BookReviewService} from './book-review-service'; import {ProgressSpinner} from 'primeng/progressspinner'; @@ -16,7 +16,7 @@ import {AppSettingsService} from '../../../../shared/service/app-settings.servic @Component({ selector: 'app-book-reviews', standalone: true, - imports: [CommonModule, ProgressSpinner, Rating, Tag, Button, FormsModule, Tooltip], + imports: [ProgressSpinner, Rating, Tag, Button, FormsModule, Tooltip], templateUrl: './book-reviews.component.html', styleUrl: './book-reviews.component.scss' }) diff --git a/booklore-ui/src/app/features/book/components/series-page/series-page.component.html b/booklore-ui/src/app/features/book/components/series-page/series-page.component.html index e640dbde..ae144e5c 100644 --- a/booklore-ui/src/app/features/book/components/series-page/series-page.component.html +++ b/booklore-ui/src/app/features/book/components/series-page/series-page.component.html @@ -1,77 +1,77 @@ @if (filteredBooks$ | async; as books) { -
- - - - - Series Details - - - - +
+ + + + + Series Details + + + + - @if (books[0]; as firstBook) { -
-
+ @if (books[0]; as firstBook) { +
+
- -
+ +
-
-
-

- {{ seriesTitle$ | async }} -

+
+
+

+ {{ seriesTitle$ | async }} +

+
+ +

+ @for (author of firstBook.metadata?.authors; track $index; let isLast = $last) { + + {{ author }} + + @if (!isLast) { + , + } + } +

+
-

- @for (author of firstBook.metadata?.authors; track $index; let isLast = $last) { - - {{ author }} - - @if (!isLast) { - , - } - } -

- -
- - @if (firstBook.metadata?.categories?.length) { -
-
- @for (category of firstBook.metadata?.categories; track category) { - - - - } -
-
- } -
-
-

- Publisher: - @if (firstBook.metadata?.publisher; as publisher) { - - {{publisher}} - - } @else { - - - } -

-

Years: {{ (yearsRange$ | async) || '-' }}

-

Number of books: {{ books.length || 0}}

-

Language: {{ firstBook.metadata?.language || "-"}}

-

Read Status: + @if (firstBook.metadata?.categories?.length) { +

+ } +
+
+

+ Publisher: + @if (firstBook.metadata?.publisher; as publisher) { + + {{publisher}} + + } @else { + - + } +

+

Years: {{ (yearsRange$ | async) || '-' }}

+

Number of books: {{ books.length || 0}}

+

Language: {{ firstBook.metadata?.language || "-"}}

+

Read Status: @let s = seriesReadStatus$ | async; {{ getStatusLabel(s) }} @if (s === 'PARTIALLY_READ') { - ({{ seriesReadProgress$ | async }}) - } + ({{ seriesReadProgress$ | async }}) + }

@@ -82,45 +82,49 @@
+ [innerHTML]="(firstDescription$ | async) || 'No description available.'">
@let desc = firstDescription$ | async; @if ((desc?.length ?? 0) > 500) { - - + + }
-
- - -
-
No books found for this series.
+ @for (book of books; track book; let i = $index) { +
+ + +
+ } + @if (books.length === 0) { +
No books found for this series.
+ }
- } + } - - - + + +
} @else { -
- - -

- Loading series details... -

-
+
+ + +

+ Loading series details... +

+
} diff --git a/booklore-ui/src/app/features/book/components/series-page/series-page.component.ts b/booklore-ui/src/app/features/book/components/series-page/series-page.component.ts index ccb24120..c8f8f132 100644 --- a/booklore-ui/src/app/features/book/components/series-page/series-page.component.ts +++ b/booklore-ui/src/app/features/book/components/series-page/series-page.component.ts @@ -2,7 +2,7 @@ import { Component, inject, ViewChild } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { Button } from "primeng/button"; import { ActivatedRoute } from "@angular/router"; -import { AsyncPipe, NgClass, NgFor, NgIf, NgStyle } from "@angular/common"; +import { AsyncPipe, NgClass, NgStyle } from "@angular/common"; import { map, filter, switchMap } from "rxjs/operators"; import { Observable, combineLatest } from "rxjs"; import { Book, ReadStatus } from "../../model/book.model"; @@ -25,8 +25,6 @@ import { Router } from "@angular/router"; AsyncPipe, Button, FormsModule, - NgIf, - NgFor, NgStyle, NgClass, BookCardComponent, @@ -37,9 +35,8 @@ import { Router } from "@angular/router"; TabPanels, TabPanel, Tag, - - VirtualScrollerModule, - ], + VirtualScrollerModule +], }) export class SeriesPageComponent { diff --git a/booklore-ui/src/app/features/metadata/component/bulk-metadata-update/bulk-metadata-update-component.ts b/booklore-ui/src/app/features/metadata/component/bulk-metadata-update/bulk-metadata-update-component.ts index 4e274957..cf2e9a87 100644 --- a/booklore-ui/src/app/features/metadata/component/bulk-metadata-update/bulk-metadata-update-component.ts +++ b/booklore-ui/src/app/features/metadata/component/bulk-metadata-update/bulk-metadata-update-component.ts @@ -1,6 +1,6 @@ import {Component, inject, OnInit} from '@angular/core'; import {FormBuilder, FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; -import {CommonModule} from '@angular/common'; + import {InputText} from 'primeng/inputtext'; import {Button} from 'primeng/button'; import {Tooltip} from 'primeng/tooltip'; @@ -18,7 +18,6 @@ import {filter, take} from "rxjs/operators"; selector: 'app-bulk-metadata-update-component', standalone: true, imports: [ - CommonModule, ReactiveFormsModule, FormsModule, InputText, @@ -28,7 +27,7 @@ import {filter, take} from "rxjs/operators"; Checkbox, ProgressSpinner, AutoComplete - ], +], providers: [MessageService], templateUrl: './bulk-metadata-update-component.html', styleUrl: './bulk-metadata-update-component.scss' diff --git a/booklore-ui/src/app/features/readers/cbx-reader/cbx-reader.component.html b/booklore-ui/src/app/features/readers/cbx-reader/cbx-reader.component.html index d67f30e5..d56f613d 100644 --- a/booklore-ui/src/app/features/readers/cbx-reader/cbx-reader.component.html +++ b/booklore-ui/src/app/features/readers/cbx-reader/cbx-reader.component.html @@ -1,14 +1,15 @@