commit 3a63c75b3acfac11fc0be50b010ac3b35c39b375 Author: RY4N Date: Fri Mar 27 00:33:48 2026 +0600 initial commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..de70529 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,61 @@ +# Dependencies +node_modules +npm-debug.log +yarn-error.log + +# Git +.git +.gitignore +.gitattributes + +# CI/CD +.github + +# Documentation +README.md +*.md +docs + +# Environment files +.env +.env.local +.env.*.local + +# IDE +.vscode +.idea +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Test files +test +tests +*.test.js +*.spec.js +coverage + +# Build artifacts +dist +build +.cache + +# Logs +logs +*.log + +# Husky and Git hooks +.husky + +# ESLint cache +.eslintcache + +# Prettier +.prettierignore + +# PM2 +.pm2 diff --git a/.github/workflow/packages.yml b/.github/workflow/packages.yml new file mode 100644 index 0000000..382a3d7 --- /dev/null +++ b/.github/workflow/packages.yml @@ -0,0 +1,169 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - master + - main + paths-ignore: + - '**.md' + - 'LICENSE' + - '.gitignore' + - 'docs/**' + pull_request: + branches: + - master + - main + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + quality-checks: + name: Code Quality & Security + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.bun/install/cache + node_modules + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock', '**/package.json') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run linter + run: bun run lint + continue-on-error: false + + - name: Check code formatting + run: bunx prettier --check . + continue-on-error: true + + - name: Run tests + run: bun run test:all + continue-on-error: true + + - name: Security audit + run: bun audit + continue-on-error: true + + build-test: + name: Build & Test + runs-on: ubuntu-latest + needs: quality-checks + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.bun/install/cache + node_modules + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock', '**/package.json') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build project + run: | + PORT=5000 bun run start & + SERVER_PID=$! + sleep 5 + kill $SERVER_PID || true + + - name: Test API endpoints + run: | + PORT=5000 bun run start & + SERVER_PID=$! + sleep 5 + curl -f http://localhost:5000/ping || exit 1 + kill $SERVER_PID || true + + publish: + name: Publish Package + runs-on: ubuntu-latest + needs: [quality-checks, build-test] + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: | + ~/.bun/install/cache + node_modules + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock', '**/package.json') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Setup Node.js for publishing + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://npm.pkg.github.com' + scope: '@${{ github.repository_owner }}' + + - name: Configure package.json for GitHub Packages + run: | + node -e ' + const fs = require("fs"); + const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); + const owner = "${{ github.repository_owner }}".toLowerCase(); + pkg.name = "@" + owner + "/" + pkg.name; + pkg.private = false; + pkg.publishConfig = { + registry: "https://npm.pkg.github.com", + access: "public" + }; + fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2)); + console.log("Updated package.json name to:", pkg.name); + ' + + - name: Publish to GitHub Packages + run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26508c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,136 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +demo.js +/index.html +/htmls +/filter.js + +tmp_rovodev_*.md diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..5971137 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +node_modules +dist +public +*.lock +*.log \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c342dfa --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "semi": true, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf" +} \ No newline at end of file diff --git a/.vercelignore b/.vercelignore new file mode 100644 index 0000000..69a461f --- /dev/null +++ b/.vercelignore @@ -0,0 +1,74 @@ +# Vercel Ignore File +# Files and directories to exclude from Vercel deployment + +# Development files +.env.local +.env.development +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Testing +coverage/ +.nyc_output/ + +# Build artifacts +dist/ +build/ +.cache/ + +# Documentation (optional - remove if you want docs in deployment) +*.md +!README.md +!DEPLOYMENT.md + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Git +.git/ +.gitignore + +# Dependencies (will be installed during build) +node_modules/ + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# CI/CD +.github/ +.gitlab-ci.yml +.travis.yml + +# Other deployment configs +render.yml +railway.json +heroku.yml + +# Development scripts +scripts/ +dev/ + +# Test files +test/ +tests/ +**/*.test.js +**/*.spec.js + +# Linting +.eslintcache +.prettierignore + +# Misc +.husky/ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..d6d79fb --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +CODE_OF_CONDUCT.md. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..76347fd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +# Use Bun's official image as base +FROM oven/bun:1 AS base +WORKDIR /app + +# Install dependencies into a temp directory +# This will cache them and speed up future builds +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lockb /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile + +# Install with --production (exclude devDependencies) +RUN mkdir -p /temp/prod +COPY package.json bun.lockb /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +# Copy node_modules from temp directory +# Then copy all (non-ignored) project files into the image +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules node_modules +COPY . . + +# Copy production dependencies and source code into final image +FROM base AS release +COPY --from=install /temp/prod/node_modules node_modules +COPY --from=prerelease /app/index.ts . +COPY --from=prerelease /app/package.json . + +# Expose the port the app runs on +EXPOSE 3000 + +# Set environment to production +ENV NODE_ENV=production + +# Run the app +USER bun +CMD ["bun", "run", "index.ts"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c2d1ac7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 RY4N + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b45e6b --- /dev/null +++ b/README.md @@ -0,0 +1,1439 @@ +# hianime-api + +
+ +![Version](https://img.shields.io/badge/version-2.0.0-blue.svg) +![License](https://img.shields.io/badge/license-MIT-green.svg) +![Bun](https://img.shields.io/badge/bun-%23000000.svg?style=flat&logo=bun&logoColor=white) +![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=flat&logo=typescript&logoColor=white) + +**A RESTful API that utilizes web scraping to fetch anime content from hianime.to** + +[Documentation](#documentation) • [Installation](#installation) • [API Endpoints](#api-endpoints) • [Development](#development) + +
+ +--- + +## Table of Contents + +- [Overview](#overview) +- [Important Notice](#important-notice) +- [Installation](#installation) + - [Prerequisites](#prerequisites) + - [Local Setup](#local-setup) +- [Deployment](#deployment) + - [Docker Deployment](#docker-deployment) + - [Vercel Deployment](#vercel-deployment-serverless--recommended) + - [Replit Deployment](#replit-deployment) +- [Video Integration](#video-integration) +- [Documentation](#documentation) + - [Anime Home Page](#1-get-anime-home-page) + - [Anime Schedule](#2-get-anime-schedule) + - [Next Episode Schedule](#3-get-next-episode-schedule) + - [Anime List Page](#4-get-anime-list-page) + - [Anime Details](#5-get-anime-detailed-info) + - [Search Results](#6-get-search-results) + - [Search Suggestions](#7-get-search-suggestions) + - [Filter Anime](#8-filter-anime) + - [Filter Options](#9-get-filter-options) + - [Anime Characters](#10-get-anime-characters) + - [Character Details](#11-get-character-details) + - [Anime Episodes](#12-get-anime-episodes) + - [Anime Schedules](#17-get-anime-schedules-7-days) + - [All Genres](#18-get-all-genres) + - [Top Airing](#19-get-top-airing) + - [Most Popular](#20-get-most-popular) + - [Most Favorite](#21-get-most-favorite) + - [Completed Anime](#22-get-completed-anime) + - [Recently Added](#23-get-recently-added) + - [Recently Updated](#24-get-recently-updated) + - [Top Upcoming](#25-get-top-upcoming) + - [Genre List](#26-get-anime-by-genre) + - [Producer List](#27-get-anime-by-producer) + - [Subbed Anime](#28-get-subbed-anime) + - [Dubbed Anime](#29-get-dubbed-anime) + - [Movies](#30-get-anime-movies) + - [TV Series](#31-get-tv-series) + - [OVA](#32-get-ova) + - [ONA](#33-get-ona) + - [Special](#34-get-special) + - [Events](#35-get-events) + - [Anime News](#37-get-anime-news) + - [Anime News](#37-get-anime-news) + - [Random Anime](#39-get-random-anime) +- [Development](#development) +- [Contributors](#contributors) +- [Acknowledgments](#acknowledgments) +- [Support](#support) + +--- + +## Overview + +hianime-api is a comprehensive RESTful API that provides endpoints to retrieve anime details, episodes, and streaming links by scraping content from hianime.to. Built with modern web technologies, it offers a robust solution for anime content aggregation. + +## Important Notice + +> ![Disclaimer](https://img.shields.io/badge/Disclaimer-red?style=for-the-badge&logo=alert&logoColor=white) + +1. This API is recommended for **personal use only**. Deploy your own instance and customize it as needed. + +2. This API is just an **unofficial API for [hianime.to](https://hianime.to)** and is in no other way officially related to the same. + +3. The content that this API provides is not mine, nor is it hosted by me. These belong to their respective owners. This API just demonstrates how to build an API that scrapes websites and uses their content. + +--- + +## Video Integration + +For video playback, it is recommended to use the following external player. You can embed it using an `iframe` with the episode ID retrieved from this API. + +**Base Embed URL:** `https://cdn.4animo.xyz/embed/` + +**Usage Example:** + +```html + +``` + +## Used By + +This API is used by the following projects: + +- **[Animo](https://animo.qzz.io/)**: A comprehensive anime streaming platform that leverages this API for real-time anime data, schedules, and streaming links. Check it out to see the API in action! + +--- + +## Installation + +### Prerequisites + +Make sure you have Bun.js installed on your system. + +**Install Bun.js:** + +```bash +https://bun.sh/docs/installation +``` + +### NPM Usage + +**Run via CLI (npx):** + +```bash +npx @ryanwtf7/hianime-api +``` + +**Install as Library:** + +```bash +npm install @ryanwtf7/hianime-api +``` + +**Usage as Library:** + +```javascript +import app from '@ryanwtf7/hianime-api'; +// Mount app or use fetch handler +``` + +### Local Setup + +**Step 1:** Clone the repository + +```bash +git clone https://github.com/ryanwtf7/hianime-api.git +``` + +**Step 2:** Navigate to the project directory + +```bash +cd hianime-api +``` + +**Step 3:** Install dependencies + +```bash +bun install +``` + +**Step 4:** Start the development server + +```bash +bun run dev +``` + +The server will be running at [http://localhost:3030](http://localhost:3030) + +--- + +## Deployment + +### Docker Deployment + +**Prerequisites:** +- Docker installed ([Install Docker](https://docs.docker.com/get-docker/)) + +**Build the Docker image:** + +```bash +docker build -t hianime-api . +``` + +**Run the container:** + +```bash +docker run -p 3030:3030 hianime-api +``` + +**With environment variables:** + +```bash +docker run -p 3030:3030 \ + -e NODE_ENV=production \ + -e PORT=3030 \ + hianime-api +``` + +**Using Docker Compose:** + +Create a `docker-compose.yml` file: + +```yaml +version: '3.8' + +services: + hianime-api: + build: . + ports: + - "3030:3030" + environment: + - NODE_ENV=production + - PORT=3030 + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3030/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s +``` + +Then run: + +```bash +docker-compose up -d +``` + +### Vercel Deployment (Serverless) ![Recommended](https://img.shields.io/badge/Recommended-blue?style=flat-square) + +**One-Click Deploy:** + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/ryanwtf7/hianime-api) + +**Manual Deployment:** + +1. Fork or clone the repository to your GitHub account +2. Sign up at [Vercel](https://vercel.com) +3. Create a new project and import your repository +4. Configure environment variables in Vercel Dashboard: + - `UPSTASH_REDIS_REST_URL` (Required - Get from [Upstash](https://upstash.com)) + - `UPSTASH_REDIS_REST_TOKEN` (Required) + - `ORIGIN=*` (or your frontend domain) + - `RATE_LIMIT_ENABLED=true` + - `RATE_LIMIT_WINDOW_MS=60000` + - `RATE_LIMIT_LIMIT=100` +5. Click "Deploy" + +**Why Vercel?** +- ![Supported](https://img.shields.io/badge/Supported-brightgreen?style=flat-square) Serverless architecture with automatic scaling +- ![Supported](https://img.shields.io/badge/Supported-brightgreen?style=flat-square) Global CDN for fast response times +- ![Supported](https://img.shields.io/badge/Supported-brightgreen?style=flat-square) Free tier with generous limits +- ![Supported](https://img.shields.io/badge/Supported-brightgreen?style=flat-square) Automatic HTTPS and custom domains +- ![Supported](https://img.shields.io/badge/Supported-brightgreen?style=flat-square) Git-based deployments (auto-deploy on push) +- ![Supported](https://img.shields.io/badge/Supported-brightgreen?style=flat-square) Built-in Redis support via Upstash + +**Environment Variables:** + +| Key | Value | Required | +|-----|-------|----------| +| `UPSTASH_REDIS_REST_URL` | Your Upstash Redis URL | Yes | +| `UPSTASH_REDIS_REST_TOKEN` | Your Upstash Redis Token | Yes | +| `ORIGIN` | `*` or your domain | No | +| `RATE_LIMIT_ENABLED` | `true` | No | +| `RATE_LIMIT_WINDOW_MS` | `60000` | No | +| `RATE_LIMIT_LIMIT` | `100` | No | + +For detailed instructions, see [DEPLOYMENT.md](./DEPLOYMENT.md) + +### Replit Deployment + +1. Import this repository into Replit +2. Click the Run button +3. Your API will be available at your Replit URL + +For detailed deployment instructions, troubleshooting, and best practices, see the [DEPLOYMENT.md](https://github.com/ryanwtf7/hianime-api/blob/master/DEPLOYMENT.md) guide. + +--- + +## Documentation + +All endpoints return JSON responses. Base URL: `/api/v2` + +### 1. GET Anime Home Page + +Retrieve the home page data including spotlight anime, trending shows, top airing, and more. + +**Endpoint:** +``` +GET /api/v2/home +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/home'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "spotlight": [...], + "trending": [...], + "topAiring": [...], + "mostPopular": [...], + "mostFavorite": [...], + "latestCompleted": [...], + "latestEpisode": [...], + "newAdded": [...], + "topUpcoming": [...], + "top10": { + "today": [...], + "week": [...], + "month": [...] + }, + "genres": [...] + } +} +``` + +
+ +--- + +### 2. GET Next Episode Schedule + +Get the next episode schedule for a specific anime. + +**Endpoint:** +``` +GET /api/v2/schedule/next/:id +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/schedule/next/one-piece-100'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "nextEpisode": { + "episodeNumber": 1120, + "releaseDate": "2024-12-15" + } + } +} +``` + +
+ +--- + +### 4. GET Anime List Page + +Retrieve anime lists based on various categories and filters. + +**Endpoint:** +``` +GET /api/v2/animes/:query/:category?page=:page +``` + +**Valid Queries:** + +| Query | Has Category | Category Options | +|-------|--------------|------------------| +| `top-airing` | No | - | +| `most-popular` | No | - | +| `most-favorite` | No | - | +| `completed` | No | - | +| `recently-added` | No | - | +| `recently-updated` | No | - | +| `top-upcoming` | No | - | +| `genre` | Yes | action, adventure, cars, comedy, dementia, demons, drama, ecchi, fantasy, game, harem, historical, horror, isekai, josei, kids, magic, martial arts, mecha, military, music, mystery, parody, police, psychological, romance, samurai, school, sci-fi, seinen, shoujo, shoujo ai, shounen, shounen ai, slice of life, space, sports, super power, supernatural, thriller, vampire | +| `producer` | Yes | Any producer slug (e.g., bones, toei-animation, mappa) | +| `az-list` | Yes | 0-9, all, a-z | +| `subbed-anime` | No | - | +| `dubbed-anime` | No | - | +| `movie` | No | - | +| `tv` | No | - | +| `ova` | No | - | +| `ona` | No | - | +| `special` | No | - | +| `events` | No | - | + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/az-list/a?page=1'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "pageInfo": { + "totalPages": 10, + "currentPage": 1, + "hasNextPage": true + }, + "animes": [ + { + "title": "Attack on Titan", + "alternativeTitle": "Shingeki no Kyojin", + "id": "attack-on-titan-112", + "poster": "https://cdn.noitatnemucod.net/thumbnail/300x400/100/...", + "episodes": { + "sub": 25, + "dub": 25, + "eps": 25 + }, + "type": "TV", + "duration": "24m" + } + ] + } +} +``` + +
+ +--- + +### 5. GET Anime Detailed Info + +Retrieve comprehensive information about a specific anime. + +**Endpoint:** +``` +GET /api/v2/anime/:id +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/anime/attack-on-titan-112'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "title": "Attack on Titan", + "alternativeTitle": "Shingeki no Kyojin", + "japanese": "進撃の巨人", + "id": "attack-on-titan-112", + "poster": "https://cdn.noitatnemucod.net/thumbnail/300x400/100/...", + "rating": "R", + "type": "TV", + "episodes": { + "sub": 25, + "dub": 25, + "eps": 25 + }, + "synopsis": "...", + "synonyms": "AoT", + "aired": { + "from": "Apr 7, 2013", + "to": "Sep 29, 2013" + }, + "premiered": "Spring 2013", + "duration": "24m", + "status": "Finished Airing", + "MAL_score": "8.52", + "genres": [...], + "studios": ["wit-studio"], + "producers": [...], + "moreSeasons": [...], + "related": [...], + "mostPopular": [...], + "recommended": [...] + } +} +``` + +
+ +--- + +### 6. GET Search Results + +Search for anime by keyword with pagination support. + +**Endpoint:** +``` +GET /api/v2/search?keyword=:query&page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/search?keyword=one+piece&page=1'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "pageInfo": { + "totalPages": 5, + "currentPage": 1, + "hasNextPage": true + }, + "animes": [ + { + "title": "One Piece", + "alternativeTitle": "One Piece", + "id": "one-piece-100", + "poster": "https://cdn.noitatnemucod.net/thumbnail/300x400/100/...", + "episodes": { + "sub": 1100, + "dub": 1050, + "eps": 1100 + }, + "type": "TV", + "duration": "24m" + } + ] + } +} +``` + +
+ +--- + +### 7. GET Search Suggestions + +Get autocomplete suggestions while searching for anime. + +**Endpoint:** +``` +GET /api/v2/suggestion?keyword=:query +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/suggestion?keyword=naruto'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": [ + { + "title": "Naruto", + "alternativeTitle": "Naruto", + "poster": "https://cdn.noitatnemucod.net/thumbnail/300x400/100/...", + "id": "naruto-677", + "aired": "Oct 3, 2002", + "type": "TV", + "duration": "23m" + } + ] +} +``` + +
+ +--- + +### 8. Filter Anime + +Filter anime based on multiple criteria. + +**Endpoint:** +``` +GET /api/v2/filter?type=:type&status=:status&rated=:rated&score=:score&season=:season&language=:language&start_date=:start_date&end_date=:end_date&sort=:sort&genres=:genres&page=:page +``` + +**Query Parameters:** + +- `type` - all, tv, movie, ova, ona, special, music +- `status` - all, finished_airing, currently_airing, not_yet_aired +- `rated` - all, g, pg, pg-13, r, r+, rx +- `score` - all, appalling, horrible, very_bad, bad, average, fine, good, very_good, great, masterpiece +- `season` - all, spring, summer, fall, winter +- `language` - all, sub, dub, sub_dub +- `start_date` - YYYY-MM-DD format +- `end_date` - YYYY-MM-DD format +- `sort` - default, recently-added, recently-updated, score, name-az, released-date, most-watched +- `genres` - Comma-separated genre slugs (action, adventure, cars, comedy, dementia, demons, mystery, drama, ecchi, fantasy, game, historical, horror, kids, magic, martial_arts, mecha, music, parody, samurai, romance, school, sci-fi, shoujo, shoujo_ai, shounen, shounen_ai, space, sports, super_power, vampire, harem, slice_of_life, supernatural, military, police, psychological, thriller, seinen, josei, isekai) +- `page` - Page number (default: 1) + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/filter?type=tv&status=currently_airing&sort=score&genres=action,fantasy&page=1'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "pageInfo": { + "totalPages": 20, + "currentPage": 1, + "hasNextPage": true + }, + "animes": [...] + } +} +``` + +
+ +--- + +### 9. GET Filter Options + +Get all available filter options. + +**Endpoint:** +``` +GET /api/v2/filter/options +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/filter/options'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "types": [...], + "statuses": [...], + "ratings": [...], + "scores": [...], + "seasons": [...], + "languages": [...], + "sorts": [...], + "genres": [...] + } +} +``` + +
+ +--- + +### 10. GET Anime Characters + +Retrieve character list for a specific anime. + +**Endpoint:** +``` +GET /api/v2/characters/:id?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/characters/one-piece-100?page=1'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "pageInfo": { + "totalPages": 5, + "currentPage": 1, + "hasNextPage": true + }, + "characters": [ + { + "name": "Monkey D. Luffy", + "image": "https://...", + "id": "character:monkey-d-luffy-1", + "role": "Main", + "voiceActors": [...] + } + ] + } +} +``` + +
+ +--- + +### 11. GET Character Details + +Get detailed information about a character or voice actor. + +**Endpoint:** +``` +GET /api/v2/character/:id +``` + +**Request Example (Character):** + +```javascript +const resp = await fetch('/api/v2/character/character:roronoa-zoro-7'); +const data = await resp.json(); +console.log(data); +``` + +**Request Example (Actor):** + +```javascript +const resp = await fetch('/api/v2/character/people:kana-hanazawa-1'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "name": "Roronoa Zoro", + "image": "https://...", + "role": "Main", + "animeAppearances": [...], + "biography": "...", + "voiceActors": [...] + } +} +``` + +
+ +--- + +### 12. GET Anime Episodes + +Retrieve the episode list for a specific anime. + +**Endpoint:** +``` +GET /api/v2/episodes/:id +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/episodes/steins-gate-3'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "totalEpisodes": 24, + "episodes": [ + { + "title": "Turning Point", + "alternativeTitle": "Hajimari to Owari no Prologue", + "episodeNumber": 1, + "id": "steinsgate-3?ep=213", + "isFiller": false + } + ] + } +} +``` + +
+ +--- + +--- + +### 17. GET Anime Schedules (7 Days) + +Retrieve anime schedules for 7 days starting from the given date (or today). + +**Endpoint:** +``` +GET /api/v2/schedules +``` + +**Query Parameters:** + +- `date` - Start date in YYYY-MM-DD format (optional, defaults to today) + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/schedules?date=2024-01-01'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "2024-01-01": [ + { + "id": "anime-id", + "time": "10:30", + "title": "Anime Title", + "jname": "Japanese Title", + "episode": 12 + } + ], + "2024-01-02": [ ... ] + } +} +``` + +
+ +--- + +### 18. GET All Genres + +Retrieve all available anime genres. + +**Endpoint:** +``` +GET /api/v2/genres +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/genres'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": [ + { + "name": "Action", + "slug": "action" + }, + { + "name": "Adventure", + "slug": "adventure" + } + ] +} +``` + +
+ +--- + +### 19. GET Top Airing + +Retrieve currently airing top anime. + +**Endpoint:** +``` +GET /api/v2/animes/top-airing?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/top-airing?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 20. GET Most Popular + +Retrieve most popular anime. + +**Endpoint:** +``` +GET /api/v2/animes/most-popular?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/most-popular?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 21. GET Most Favorite + +Retrieve most favorited anime. + +**Endpoint:** +``` +GET /api/v2/animes/most-favorite?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/most-favorite?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 22. GET Completed Anime + +Retrieve completed anime series. + +**Endpoint:** +``` +GET /api/v2/animes/completed?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/completed?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 23. GET Recently Added + +Retrieve recently added anime. + +**Endpoint:** +``` +GET /api/v2/animes/recently-added?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/recently-added?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 24. GET Recently Updated + +Retrieve recently updated anime. + +**Endpoint:** +``` +GET /api/v2/animes/recently-updated?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/recently-updated?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 25. GET Top Upcoming + +Retrieve top upcoming anime. + +**Endpoint:** +``` +GET /api/v2/animes/top-upcoming?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/top-upcoming?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 26. GET Anime by Genre + +Retrieve anime filtered by specific genre. + +**Endpoint:** +``` +GET /api/v2/animes/genre/:genre?page=:page +``` + +**Available Genres:** action, adventure, cars, comedy, dementia, demons, drama, ecchi, fantasy, game, harem, historical, horror, isekai, josei, kids, magic, martial arts, mecha, military, music, mystery, parody, police, psychological, romance, samurai, school, sci-fi, seinen, shoujo, shoujo ai, shounen, shounen ai, slice of life, space, sports, super power, supernatural, thriller, vampire + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/genre/action?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 27. GET Anime by Producer + +Retrieve anime filtered by production studio or company. + +**Endpoint:** +``` +GET /api/v2/animes/producer/:producer?page=:page +``` + +**Producer Examples:** bones, toei-animation, mappa, ufotable, kyoto-animation, wit-studio, madhouse, a-1-pictures, trigger, cloverworks + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/producer/bones?page=1'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "pageInfo": { + "totalPages": 15, + "currentPage": 1, + "hasNextPage": true + }, + "animes": [ + { + "title": "My Hero Academia", + "alternativeTitle": "Boku no Hero Academia", + "id": "my-hero-academia-67", + "poster": "https://cdn.noitatnemucod.net/thumbnail/300x400/100/...", + "episodes": { + "sub": 13, + "dub": 13, + "eps": 13 + }, + "type": "TV", + "duration": "24m" + } + ] + } +} +``` + +
+ +--- + +### 28. GET Subbed Anime + +Retrieve anime with subtitles available. + +**Endpoint:** +``` +GET /api/v2/animes/subbed-anime?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/subbed-anime?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 29. GET Dubbed Anime + +Retrieve anime with English dub available. + +**Endpoint:** +``` +GET /api/v2/animes/dubbed-anime?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/dubbed-anime?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 30. GET Anime Movies + +Retrieve anime movies. + +**Endpoint:** +``` +GET /api/v2/animes/movie?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/movie?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 31. GET TV Series + +Retrieve anime TV series. + +**Endpoint:** +``` +GET /api/v2/animes/tv?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/tv?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 32. GET OVA + +Retrieve Original Video Animation (OVA) content. + +**Endpoint:** +``` +GET /api/v2/animes/ova?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/ova?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 33. GET ONA + +Retrieve Original Net Animation (ONA) content. + +**Endpoint:** +``` +GET /api/v2/animes/ona?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/ona?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 34. GET Special + +Retrieve special anime episodes. + +**Endpoint:** +``` +GET /api/v2/animes/special?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/special?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +### 35. GET Events + +Retrieve anime events. + +**Endpoint:** +``` +GET /api/v2/animes/events?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/animes/events?page=1'); +const data = await resp.json(); +console.log(data); +``` + +--- + +--- + +## Development + +Pull requests and stars are always welcome. If you encounter any bug or want to add a new feature to this API, consider creating a new [issue](https://github.com/ryanwtf7/hianime-api/issues). If you wish to contribute to this project, feel free to make a pull request. + +### Running in Development Mode + +```bash +bun run dev +``` + +### Running in Production Mode + +```bash +bun start +``` + +--- + +### 37. GET Anime News + +Retrieve latest anime news articles. + +**Endpoint:** +``` +GET /api/v2/news?page=:page +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/news?page=1'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "news": [ + { + "id": "article-id", + "title": "Article Title", + "description": "...", + "thumbnail": "https://...", + "uploadedAt": "2 hours ago" + } + ] + } +} +``` + +
+ +--- + +--- + +### 39. GET Random Anime + +Retrieve a random anime ID. + +**Endpoint:** +``` +GET /api/v2/random +``` + +**Request Example:** + +```javascript +const resp = await fetch('/api/v2/random'); +const data = await resp.json(); +console.log(data); +``` + +**Response Schema:** + +
+Example + +```javascript +{ + "success": true, + "data": { + "id": "anime-id-123" + } +} +``` + +
+ +--- + +## Contributors + +Thanks to the following people for keeping this project alive and relevant: + + + Contributors + + +Want to contribute? Check out our [contribution guidelines](https://github.com/ryanwtf7/hianime-api/blob/main/CONTRIBUTING.md) and feel free to submit a pull request! + +--- + +## Acknowledgments + +Special thanks to the following projects for inspiration and reference: + +- [consumet.ts](https://github.com/consumet/consumet.ts) +- [api.consumet.org](https://github.com/consumet/api.consumet.org) + +--- + +## Support + +If you find this project useful, please consider giving it a star on GitHub! + +[![GitHub stars](https://img.shields.io/github/stars/ryanwtf7/hianime-api?style=social)](https://github.com/ryanwtf7/hianime-api/stargazers) + +--- + +
+ +**Made by RY4N** + +[Report Bug](https://github.com/ryanwtf7/hianime-api/issues) • [Request Feature](https://github.com/ryanwtf7/hianime-api/issues) + +
diff --git a/api/index.ts b/api/index.ts new file mode 100644 index 0000000..ba84ad5 --- /dev/null +++ b/api/index.ts @@ -0,0 +1,46 @@ +import app from '../src/app'; + +type VercelResponse = { + status: (code: number) => VercelResponse; + setHeader: (name: string, value: string) => VercelResponse; + send: (body: string | object | Buffer) => VercelResponse; + json: (body: object) => VercelResponse; +}; + +export default async function handler( + req: { + headers: Record; + method: string; + url: string; + body: unknown; + }, + res: VercelResponse +) { + try { + const protocol = req.headers['x-forwarded-proto'] || 'https'; + const host = req.headers['x-forwarded-host'] || req.headers.host; + const url = `${protocol}://${host}${req.url}`; + const webRequest = new Request(url, { + method: req.method, + headers: new Headers(req.headers as Record), + body: req.method !== 'GET' && req.method !== 'HEAD' ? JSON.stringify(req.body) : undefined, + }); + + const webResponse = await app.fetch(webRequest); + res.status(webResponse.status); + webResponse.headers.forEach((value: string, key: string) => { + res.setHeader(key, value); + }); + + const body = await webResponse.text(); + res.send(body); + } catch (error: unknown) { + const err = error as Error; + console.error('Vercel handler error:', err); + res.status(500).json({ + success: false, + error: 'Internal Server Error', + message: err.message, + }); + } +} diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..4805ef4 --- /dev/null +++ b/bun.lock @@ -0,0 +1,1113 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "4animo", + "dependencies": { + "@hono/node-server": "^1.8.2", + "axios": "^1.13.6", + "cheerio": "^1.0.0-rc.12", + }, + "devDependencies": { + "@types/axios": "^0.14.4", + "@types/jest": "^30.0.0", + "@types/node": "^25.5.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "@vitest/ui": "^4.1.1", + "bun-types": "^1.3.11", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "globals": "^17.4.0", + "jest": "^30.3.0", + "prettier": "^3.2.5", + "ts-jest": "^29.4.6", + "typescript": "^5.3.3", + "vitest": "^4.1.1", + }, + }, + }, + "packages": { + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], + + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + + "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="], + + "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="], + + "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="], + + "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="], + + "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw=="], + + "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="], + + "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="], + + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="], + + "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="], + + "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="], + + "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="], + + "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="], + + "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="], + + "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="], + + "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="], + + "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="], + + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="], + + "@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" } }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@2.1.4", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ=="], + + "@eslint/js": ["@eslint/js@8.57.1", "", {}, "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q=="], + + "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], + + "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/object-schema": ["@humanwhocodes/object-schema@2.0.3", "", {}, "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA=="], + + "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "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" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="], + + "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="], + + "@jest/console": ["@jest/console@30.3.0", "", { "dependencies": { "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "jest-message-util": "30.3.0", "jest-util": "30.3.0", "slash": "^3.0.0" } }, "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww=="], + + "@jest/core": ["@jest/core@30.3.0", "", { "dependencies": { "@jest/console": "30.3.0", "@jest/pattern": "30.0.1", "@jest/reporters": "30.3.0", "@jest/test-result": "30.3.0", "@jest/transform": "30.3.0", "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-changed-files": "30.3.0", "jest-config": "30.3.0", "jest-haste-map": "30.3.0", "jest-message-util": "30.3.0", "jest-regex-util": "30.0.1", "jest-resolve": "30.3.0", "jest-resolve-dependencies": "30.3.0", "jest-runner": "30.3.0", "jest-runtime": "30.3.0", "jest-snapshot": "30.3.0", "jest-util": "30.3.0", "jest-validate": "30.3.0", "jest-watcher": "30.3.0", "pretty-format": "30.3.0", "slash": "^3.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw=="], + + "@jest/diff-sequences": ["@jest/diff-sequences@30.3.0", "", {}, "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA=="], + + "@jest/environment": ["@jest/environment@30.3.0", "", { "dependencies": { "@jest/fake-timers": "30.3.0", "@jest/types": "30.3.0", "@types/node": "*", "jest-mock": "30.3.0" } }, "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw=="], + + "@jest/expect": ["@jest/expect@30.3.0", "", { "dependencies": { "expect": "30.3.0", "jest-snapshot": "30.3.0" } }, "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg=="], + + "@jest/expect-utils": ["@jest/expect-utils@30.3.0", "", { "dependencies": { "@jest/get-type": "30.1.0" } }, "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA=="], + + "@jest/fake-timers": ["@jest/fake-timers@30.3.0", "", { "dependencies": { "@jest/types": "30.3.0", "@sinonjs/fake-timers": "^15.0.0", "@types/node": "*", "jest-message-util": "30.3.0", "jest-mock": "30.3.0", "jest-util": "30.3.0" } }, "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ=="], + + "@jest/get-type": ["@jest/get-type@30.1.0", "", {}, "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA=="], + + "@jest/globals": ["@jest/globals@30.3.0", "", { "dependencies": { "@jest/environment": "30.3.0", "@jest/expect": "30.3.0", "@jest/types": "30.3.0", "jest-mock": "30.3.0" } }, "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA=="], + + "@jest/pattern": ["@jest/pattern@30.0.1", "", { "dependencies": { "@types/node": "*", "jest-regex-util": "30.0.1" } }, "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA=="], + + "@jest/reporters": ["@jest/reporters@30.3.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "30.3.0", "@jest/test-result": "30.3.0", "@jest/transform": "30.3.0", "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", "glob": "^10.5.0", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "30.3.0", "jest-util": "30.3.0", "jest-worker": "30.3.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw=="], + + "@jest/schemas": ["@jest/schemas@30.0.5", "", { "dependencies": { "@sinclair/typebox": "^0.34.0" } }, "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA=="], + + "@jest/snapshot-utils": ["@jest/snapshot-utils@30.3.0", "", { "dependencies": { "@jest/types": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" } }, "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g=="], + + "@jest/source-map": ["@jest/source-map@30.0.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "callsites": "^3.1.0", "graceful-fs": "^4.2.11" } }, "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg=="], + + "@jest/test-result": ["@jest/test-result@30.3.0", "", { "dependencies": { "@jest/console": "30.3.0", "@jest/types": "30.3.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" } }, "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ=="], + + "@jest/test-sequencer": ["@jest/test-sequencer@30.3.0", "", { "dependencies": { "@jest/test-result": "30.3.0", "graceful-fs": "^4.2.11", "jest-haste-map": "30.3.0", "slash": "^3.0.0" } }, "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA=="], + + "@jest/transform": ["@jest/transform@30.3.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@jest/types": "30.3.0", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", "jest-haste-map": "30.3.0", "jest-regex-util": "30.0.1", "jest-util": "30.3.0", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" } }, "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A=="], + + "@jest/types": ["@jest/types@30.3.0", "", { "dependencies": { "@jest/pattern": "30.0.1", "@jest/schemas": "30.0.5", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", "@types/yargs": "^17.0.33", "chalk": "^4.1.2" } }, "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@oxc-project/types": ["@oxc-project/types@0.122.0", "", {}, "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA=="], + + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], + + "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.12", "", { "os": "android", "cpu": "arm64" }, "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm" }, "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.12", "", { "os": "linux", "cpu": "x64" }, "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.12", "", { "os": "none", "cpu": "arm64" }, "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.12", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "x64" }, "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.12", "", {}, "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw=="], + + "@sinclair/typebox": ["@sinclair/typebox@0.34.48", "", {}, "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA=="], + + "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="], + + "@sinonjs/fake-timers": ["@sinonjs/fake-timers@15.1.1", "", { "dependencies": { "@sinonjs/commons": "^3.0.1" } }, "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@types/axios": ["@types/axios@0.14.4", "", { "dependencies": { "axios": "*" } }, "sha512-9JgOaunvQdsQ/qW2OPmE5+hCeUB52lQSolecrFrthct55QekhmXEwT203s20RL+UHtCQc15y3VXpby9E7Kkh/g=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="], + + "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="], + + "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="], + + "@types/jest": ["@types/jest@30.0.0", "", { "dependencies": { "expect": "^30.0.0", "pretty-format": "^30.0.0" } }, "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA=="], + + "@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="], + + "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], + + "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="], + + "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.18.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/type-utils": "7.18.0", "@typescript-eslint/utils": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@7.18.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@7.18.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@7.18.0", "", {}, "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" } }, "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@7.18.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="], + + "@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.11.1", "", { "os": "android", "cpu": "arm64" }, "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g=="], + + "@unrs/resolver-binding-darwin-arm64": ["@unrs/resolver-binding-darwin-arm64@1.11.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g=="], + + "@unrs/resolver-binding-darwin-x64": ["@unrs/resolver-binding-darwin-x64@1.11.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ=="], + + "@unrs/resolver-binding-freebsd-x64": ["@unrs/resolver-binding-freebsd-x64@1.11.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw=="], + + "@unrs/resolver-binding-linux-arm-gnueabihf": ["@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw=="], + + "@unrs/resolver-binding-linux-arm-musleabihf": ["@unrs/resolver-binding-linux-arm-musleabihf@1.11.1", "", { "os": "linux", "cpu": "arm" }, "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw=="], + + "@unrs/resolver-binding-linux-arm64-gnu": ["@unrs/resolver-binding-linux-arm64-gnu@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ=="], + + "@unrs/resolver-binding-linux-arm64-musl": ["@unrs/resolver-binding-linux-arm64-musl@1.11.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w=="], + + "@unrs/resolver-binding-linux-ppc64-gnu": ["@unrs/resolver-binding-linux-ppc64-gnu@1.11.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA=="], + + "@unrs/resolver-binding-linux-riscv64-gnu": ["@unrs/resolver-binding-linux-riscv64-gnu@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ=="], + + "@unrs/resolver-binding-linux-riscv64-musl": ["@unrs/resolver-binding-linux-riscv64-musl@1.11.1", "", { "os": "linux", "cpu": "none" }, "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew=="], + + "@unrs/resolver-binding-linux-s390x-gnu": ["@unrs/resolver-binding-linux-s390x-gnu@1.11.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg=="], + + "@unrs/resolver-binding-linux-x64-gnu": ["@unrs/resolver-binding-linux-x64-gnu@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w=="], + + "@unrs/resolver-binding-linux-x64-musl": ["@unrs/resolver-binding-linux-x64-musl@1.11.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA=="], + + "@unrs/resolver-binding-wasm32-wasi": ["@unrs/resolver-binding-wasm32-wasi@1.11.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, "cpu": "none" }, "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ=="], + + "@unrs/resolver-binding-win32-arm64-msvc": ["@unrs/resolver-binding-win32-arm64-msvc@1.11.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw=="], + + "@unrs/resolver-binding-win32-ia32-msvc": ["@unrs/resolver-binding-win32-ia32-msvc@1.11.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ=="], + + "@unrs/resolver-binding-win32-x64-msvc": ["@unrs/resolver-binding-win32-x64-msvc@1.11.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g=="], + + "@vitest/expect": ["@vitest/expect@4.1.1", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.1", "@vitest/utils": "4.1.1", "chai": "^6.2.2", "tinyrainbow": "^3.0.3" } }, "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.1", "", { "dependencies": { "@vitest/spy": "4.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.1", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ=="], + + "@vitest/runner": ["@vitest/runner@4.1.1", "", { "dependencies": { "@vitest/utils": "4.1.1", "pathe": "^2.0.3" } }, "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.1", "", { "dependencies": { "@vitest/pretty-format": "4.1.1", "@vitest/utils": "4.1.1", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg=="], + + "@vitest/spy": ["@vitest/spy@4.1.1", "", {}, "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA=="], + + "@vitest/ui": ["@vitest/ui@4.1.1", "", { "dependencies": { "@vitest/utils": "4.1.1", "fflate": "^0.8.2", "flatted": "3.4.0", "pathe": "^2.0.3", "sirv": "^3.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3" }, "peerDependencies": { "vitest": "4.1.1" } }, "sha512-k0qNVLmCISxoGWvdhOeynlZVrfjx7Xjp95kIptN0fZYyONCgVcKIPn53MpFZ7S+fO6YdKNhgIfl0nu92Q0CCOg=="], + + "@vitest/utils": ["@vitest/utils@4.1.1", "", { "dependencies": { "@vitest/pretty-format": "4.1.1", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ=="], + + "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], + + "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + + "axios": ["axios@1.13.6", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ=="], + + "babel-jest": ["babel-jest@30.3.0", "", { "dependencies": { "@jest/transform": "30.3.0", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", "babel-preset-jest": "30.3.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ=="], + + "babel-plugin-istanbul": ["babel-plugin-istanbul@7.0.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" } }, "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA=="], + + "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@30.3.0", "", { "dependencies": { "@types/babel__core": "^7.20.5" } }, "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg=="], + + "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="], + + "babel-preset-jest": ["babel-preset-jest@30.3.0", "", { "dependencies": { "babel-plugin-jest-hoist": "30.3.0", "babel-preset-current-node-syntax": "^1.2.0" }, "peerDependencies": { "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-DAKrHphkJyiGuau/cFieRYhcTFeK/lBuD++C7cZ6KZHbMhBrisoi+EvhQ5RZrIfV5qwsW8kgQ07JIC+MDJRAhg=="], + + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "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.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], + + "bs-logger": ["bs-logger@0.2.6", "", { "dependencies": { "fast-json-stable-stringify": "2.x" } }, "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog=="], + + "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="], + + "cheerio": ["cheerio@1.2.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.1.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg=="], + + "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], + + "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], + + "cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], + + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="], + + "collect-v8-coverage": ["collect-v8-coverage@1.0.3", "", {}, "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "dedent": ["dedent@1.7.2", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "doctrine": ["doctrine@3.0.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w=="], + + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.325", "", {}, "sha512-PwfIw7WQSt3xX7yOf5OE/unLzsK9CaN2f/FvV3WjPR1Knoc1T9vePRVV4W1EM301JzzysK51K7FNKcusCr0zYA=="], + + "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "error-ex": ["error-ex@1.3.4", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@8.57.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", "@eslint/js": "8.57.1", "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.2.2", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" } }, "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA=="], + + "eslint-config-prettier": ["eslint-config-prettier@9.1.2", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ=="], + + "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.5", "", { "dependencies": { "prettier-linter-helpers": "^1.0.1", "synckit": "^0.11.12" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw=="], + + "eslint-scope": ["eslint-scope@7.2.2", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + + "exit-x": ["exit-x@0.2.2", "", {}, "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ=="], + + "expect": ["expect@30.3.0", "", { "dependencies": { "@jest/expect-utils": "30.3.0", "@jest/get-type": "30.1.0", "jest-matcher-utils": "30.3.0", "jest-message-util": "30.3.0", "jest-mock": "30.3.0", "jest-util": "30.3.0" } }, "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q=="], + + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], + + "file-entry-cache": ["file-entry-cache@6.0.1", "", { "dependencies": { "flat-cache": "^3.0.4" } }, "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], + + "flatted": ["flatted@3.4.0", "", {}, "sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw=="], + + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], + + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + + "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + + "htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], + + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="], + + "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-lib-source-maps": ["istanbul-lib-source-maps@5.0.6", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0" } }, "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + + "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "jest": ["jest@30.3.0", "", { "dependencies": { "@jest/core": "30.3.0", "@jest/types": "30.3.0", "import-local": "^3.2.0", "jest-cli": "30.3.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": "./bin/jest.js" }, "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg=="], + + "jest-changed-files": ["jest-changed-files@30.3.0", "", { "dependencies": { "execa": "^5.1.1", "jest-util": "30.3.0", "p-limit": "^3.1.0" } }, "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA=="], + + "jest-circus": ["jest-circus@30.3.0", "", { "dependencies": { "@jest/environment": "30.3.0", "@jest/expect": "30.3.0", "@jest/test-result": "30.3.0", "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", "jest-each": "30.3.0", "jest-matcher-utils": "30.3.0", "jest-message-util": "30.3.0", "jest-runtime": "30.3.0", "jest-snapshot": "30.3.0", "jest-util": "30.3.0", "p-limit": "^3.1.0", "pretty-format": "30.3.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" } }, "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA=="], + + "jest-cli": ["jest-cli@30.3.0", "", { "dependencies": { "@jest/core": "30.3.0", "@jest/test-result": "30.3.0", "@jest/types": "30.3.0", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", "jest-config": "30.3.0", "jest-util": "30.3.0", "jest-validate": "30.3.0", "yargs": "^17.7.2" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "./bin/jest.js" } }, "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw=="], + + "jest-config": ["jest-config@30.3.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", "@jest/test-sequencer": "30.3.0", "@jest/types": "30.3.0", "babel-jest": "30.3.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.5.0", "graceful-fs": "^4.2.11", "jest-circus": "30.3.0", "jest-docblock": "30.2.0", "jest-environment-node": "30.3.0", "jest-regex-util": "30.0.1", "jest-resolve": "30.3.0", "jest-runner": "30.3.0", "jest-util": "30.3.0", "jest-validate": "30.3.0", "parse-json": "^5.2.0", "pretty-format": "30.3.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "esbuild-register", "ts-node"] }, "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w=="], + + "jest-diff": ["jest-diff@30.3.0", "", { "dependencies": { "@jest/diff-sequences": "30.3.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", "pretty-format": "30.3.0" } }, "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ=="], + + "jest-docblock": ["jest-docblock@30.2.0", "", { "dependencies": { "detect-newline": "^3.1.0" } }, "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA=="], + + "jest-each": ["jest-each@30.3.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.3.0", "chalk": "^4.1.2", "jest-util": "30.3.0", "pretty-format": "30.3.0" } }, "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA=="], + + "jest-environment-node": ["jest-environment-node@30.3.0", "", { "dependencies": { "@jest/environment": "30.3.0", "@jest/fake-timers": "30.3.0", "@jest/types": "30.3.0", "@types/node": "*", "jest-mock": "30.3.0", "jest-util": "30.3.0", "jest-validate": "30.3.0" } }, "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ=="], + + "jest-haste-map": ["jest-haste-map@30.3.0", "", { "dependencies": { "@jest/types": "30.3.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", "jest-util": "30.3.0", "jest-worker": "30.3.0", "picomatch": "^4.0.3", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.3" } }, "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA=="], + + "jest-leak-detector": ["jest-leak-detector@30.3.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "pretty-format": "30.3.0" } }, "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ=="], + + "jest-matcher-utils": ["jest-matcher-utils@30.3.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", "jest-diff": "30.3.0", "pretty-format": "30.3.0" } }, "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA=="], + + "jest-message-util": ["jest-message-util@30.3.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@jest/types": "30.3.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "picomatch": "^4.0.3", "pretty-format": "30.3.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" } }, "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw=="], + + "jest-mock": ["jest-mock@30.3.0", "", { "dependencies": { "@jest/types": "30.3.0", "@types/node": "*", "jest-util": "30.3.0" } }, "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog=="], + + "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="], + + "jest-regex-util": ["jest-regex-util@30.0.1", "", {}, "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA=="], + + "jest-resolve": ["jest-resolve@30.3.0", "", { "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "jest-haste-map": "30.3.0", "jest-pnp-resolver": "^1.2.3", "jest-util": "30.3.0", "jest-validate": "30.3.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" } }, "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g=="], + + "jest-resolve-dependencies": ["jest-resolve-dependencies@30.3.0", "", { "dependencies": { "jest-regex-util": "30.0.1", "jest-snapshot": "30.3.0" } }, "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw=="], + + "jest-runner": ["jest-runner@30.3.0", "", { "dependencies": { "@jest/console": "30.3.0", "@jest/environment": "30.3.0", "@jest/test-result": "30.3.0", "@jest/transform": "30.3.0", "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", "jest-docblock": "30.2.0", "jest-environment-node": "30.3.0", "jest-haste-map": "30.3.0", "jest-leak-detector": "30.3.0", "jest-message-util": "30.3.0", "jest-resolve": "30.3.0", "jest-runtime": "30.3.0", "jest-util": "30.3.0", "jest-watcher": "30.3.0", "jest-worker": "30.3.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw=="], + + "jest-runtime": ["jest-runtime@30.3.0", "", { "dependencies": { "@jest/environment": "30.3.0", "@jest/fake-timers": "30.3.0", "@jest/globals": "30.3.0", "@jest/source-map": "30.0.1", "@jest/test-result": "30.3.0", "@jest/transform": "30.3.0", "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.5.0", "graceful-fs": "^4.2.11", "jest-haste-map": "30.3.0", "jest-message-util": "30.3.0", "jest-mock": "30.3.0", "jest-regex-util": "30.0.1", "jest-resolve": "30.3.0", "jest-snapshot": "30.3.0", "jest-util": "30.3.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng=="], + + "jest-snapshot": ["jest-snapshot@30.3.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", "@jest/expect-utils": "30.3.0", "@jest/get-type": "30.1.0", "@jest/snapshot-utils": "30.3.0", "@jest/transform": "30.3.0", "@jest/types": "30.3.0", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", "expect": "30.3.0", "graceful-fs": "^4.2.11", "jest-diff": "30.3.0", "jest-matcher-utils": "30.3.0", "jest-message-util": "30.3.0", "jest-util": "30.3.0", "pretty-format": "30.3.0", "semver": "^7.7.2", "synckit": "^0.11.8" } }, "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ=="], + + "jest-util": ["jest-util@30.3.0", "", { "dependencies": { "@jest/types": "30.3.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", "graceful-fs": "^4.2.11", "picomatch": "^4.0.3" } }, "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg=="], + + "jest-validate": ["jest-validate@30.3.0", "", { "dependencies": { "@jest/get-type": "30.1.0", "@jest/types": "30.3.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", "pretty-format": "30.3.0" } }, "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q=="], + + "jest-watcher": ["jest-watcher@30.3.0", "", { "dependencies": { "@jest/test-result": "30.3.0", "@jest/types": "30.3.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", "jest-util": "30.3.0", "string-length": "^4.0.2" } }, "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w=="], + + "jest-worker": ["jest-worker@30.3.0", "", { "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", "jest-util": "30.3.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" } }, "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + + "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="], + + "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + + "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], + + "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], + + "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], + + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-json": ["parse-json@5.2.0", "", { "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" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], + + "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], + + "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="], + + "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.8.1", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg=="], + + "prettier-linter-helpers": ["prettier-linter-helpers@1.0.1", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg=="], + + "pretty-format": ["pretty-format@30.3.0", "", { "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" } }, "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "pure-rand": ["pure-rand@7.0.1", "", {}, "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "rolldown": ["rolldown@1.0.0-rc.12", "", { "dependencies": { "@oxc-project/types": "=0.122.0", "@rolldown/pluginutils": "1.0.0-rc.12" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", "@rolldown/binding-darwin-x64": "1.0.0-rc.12", "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "sirv": ["sirv@3.0.2", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g=="], + + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], + + "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="], + + "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "synckit": ["synckit@0.11.12", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="], + + "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="], + + "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], + + "ts-api-utils": ["ts-api-utils@1.4.3", "", { "peerDependencies": { "typescript": ">=4.2.0" } }, "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw=="], + + "ts-jest": ["ts-jest@29.4.6", "", { "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", "handlebars": "^4.7.8", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.3", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/transform": "^29.0.0 || ^30.0.0", "@jest/types": "^29.0.0 || ^30.0.0", "babel-jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0", "typescript": ">=4.3 <6" }, "optionalPeers": ["@babel/core", "@jest/transform", "@jest/types", "babel-jest", "jest-util"], "bin": { "ts-jest": "cli.js" } }, "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], + + "undici": ["undici@7.22.0", "", {}, "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg=="], + + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "unrs-resolver": ["unrs-resolver@1.11.1", "", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="], + + "vite": ["vite@8.0.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ=="], + + "vitest": ["vitest@4.1.1", "", { "dependencies": { "@vitest/expect": "4.1.1", "@vitest/mocker": "4.1.1", "@vitest/pretty-format": "4.1.1", "@vitest/runner": "4.1.1", "@vitest/snapshot": "4.1.1", "@vitest/spy": "4.1.1", "@vitest/utils": "4.1.1", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.1", "@vitest/browser-preview": "4.1.1", "@vitest/browser-webdriverio": "4.1.1", "@vitest/ui": "4.1.1", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA=="], + + "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="], + + "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="], + + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "write-file-atomic": ["write-file-atomic@5.0.1", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "@eslint/eslintrc/globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + + "@istanbuljs/load-nyc-config/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "@unrs/resolver-binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "eslint/globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], + + "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "flat-cache/flatted": ["flatted@3.4.1", "", {}, "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ=="], + + "glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + + "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + + "test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "@eslint/eslintrc/globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], + + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "eslint/globals/type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + } +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..25e05c1 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,40 @@ +import js from '@eslint/js'; +import tseslint from '@typescript-eslint/eslint-plugin'; +import tsparser from '@typescript-eslint/parser'; +import prettier from 'eslint-plugin-prettier'; +import prettierConfig from 'eslint-config-prettier'; +import globals from 'globals'; + +export default [ + js.configs.recommended, + { + files: ['**/*.{js,ts}'], + languageOptions: { + parser: tsparser, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module', + }, + globals: { + ...globals.node, + ...globals.browser, + Bun: 'readonly', + }, + }, + plugins: { + '@typescript-eslint': tseslint, + prettier: prettier, + }, + rules: { + ...tseslint.configs.recommended.rules, + ...prettierConfig.rules, + 'prettier/prettier': 'error', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'warn', + 'no-console': 'off', + }, + }, + { + ignores: ['node_modules/**', 'dist/**', 'public/**'], + }, +]; diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..8f13ffd --- /dev/null +++ b/index.ts @@ -0,0 +1,12 @@ +import app from './src/app'; +import config from './src/config/config'; + +const PORT = config.port; + +Bun.serve({ + port: PORT, + hostname: '0.0.0.0', + fetch: app.fetch, +}); + +console.log(`Server running at http://0.0.0.0:${PORT}`); diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 0000000..d456ad4 --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,17 @@ +export default { + preset: 'ts-jest', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1', + }, + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + useESM: true, + }, + ], + }, + testMatch: ['**/scripts/tests/jest/**/*.test.ts'], +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..967129a --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "hianime-api", + "version": "2.0.0", + "type": "module", + "scripts": { + "dev": "bun run --hot index.ts", + "start": "bun index.ts", + "build": "bun build index.ts --outdir ./dist --target bun", + "lint": "eslint . --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", + "type-check": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:jest": "NODE_OPTIONS='--experimental-vm-modules' jest", + "test:all": "bun run test && bun run test:jest", + "clean": "rm -rf dist node_modules/.cache" + }, + "dependencies": { + "@hono/node-server": "^1.8.2", + "axios": "^1.13.6", + "cheerio": "^1.0.0-rc.12" + }, + "devDependencies": { + "@types/axios": "^0.14.4", + "@types/jest": "^30.0.0", + "@types/node": "^25.5.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", + "@vitest/ui": "^4.1.1", + "bun-types": "^1.3.11", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "globals": "^17.4.0", + "jest": "^30.3.0", + "prettier": "^3.2.5", + "ts-jest": "^29.4.6", + "typescript": "^5.3.3", + "vitest": "^4.1.1" + } +} \ No newline at end of file diff --git a/scripts/tests/data/mocks.ts b/scripts/tests/data/mocks.ts new file mode 100644 index 0000000..ba3a2e3 --- /dev/null +++ b/scripts/tests/data/mocks.ts @@ -0,0 +1,214 @@ +export const mockHtmlData = { + homepage: ` +
+
+
+
+ +
+
Spotlight Anime
+
Description text
+
+ +
+
+ TV + 24m + Oct 1, 2023 + 12 + 10 + 12 +
+
+
+
+ + + `, + detail: ` +
+
+
+ +
18+
+
+
+

Detail Anime

+
+
+ TV + 12 + 10 + 12 +
+
+
+ +
+
+
+
+
+ Japanese: + 日本語 +
+
+ Aired: + Oct 1, 2023 to ? +
+
+
+
+
+ `, + search: ` +
+
+
+
+ + +
+
+

Search Result

+
+ TV +
+
+
+
+
+ `, + characters: ` +
+
+
+
+
+ + + +
+
+ +
Main
+
+
+
+
+
+ `, + news: ` +
+
+ +

News Title

+
News description
+ +
Oct 1, 2023
+
+
+ `, + schedule: ` + + `, + episodes: ` +
+
+ +
+
+
+
+ `, + characterDetail: ` +
+
+ +
+
+
Character Full Name
+
Character Japanese Name
+
+
+

Character biography

+
+
+
+
+ `, + suggestions: ` + + + + `, + topSearch: ` +
+ T1 + T2 + Top Title +
+ `, + scheduleNext: ` +
+
+
+ `, +}; diff --git a/scripts/tests/jest/controllers/comprehensive.test.ts b/scripts/tests/jest/controllers/comprehensive.test.ts new file mode 100644 index 0000000..79730cf --- /dev/null +++ b/scripts/tests/jest/controllers/comprehensive.test.ts @@ -0,0 +1,140 @@ +import { describe, it, expect, jest, beforeEach } from '@jest/globals'; +import { Context } from 'hono'; +import homepageController from '../../../../src/controllers/homepage.controller'; +import detailpageController from '../../../../src/controllers/detailpage.controller'; +import searchController from '../../../../src/controllers/search.controller'; +import episodesController from '../../../../src/controllers/episodes.controller'; +import charactersController from '../../../../src/controllers/characters.controller'; +import characterDetailController from '../../../../src/controllers/characterDetail.controller'; +import listpageController from '../../../../src/controllers/listpage.controller'; +import topSearchController from '../../../../src/controllers/topSearch.controller'; +import schedulesController from '../../../../src/controllers/schedules.controller'; +import newsController from '../../../../src/controllers/news.controller'; +import suggestionController from '../../../../src/controllers/suggestion.controller'; +import nextEpisodeScheduleController from '../../../../src/controllers/nextEpisodeSchedule.controller'; +import randomController from '../../../../src/controllers/random.controller'; +import filterController from '../../../../src/controllers/filter.controller'; +import allGenresController from '../../../../src/controllers/allGenres.controller'; +import { mockHtmlData } from '../../data/mocks'; + +// Mock global fetch since axiosInstance uses it +const mockFetch = jest.fn(); +global.fetch = mockFetch as unknown as typeof global.fetch; + +const createMockContext = ( + params: Record = {}, + query: Record = {} +) => { + return { + req: { + param: (name?: string) => (name ? params[name] : params), + query: (name?: string) => (name ? query[name] : query), + }, + json: jest.fn((data: unknown) => data), + } as unknown as Context; +}; + +describe('Controllers Comprehensive Suite (Jest)', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const mockSuccess = (data: string) => { + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + text: () => Promise.resolve(data), + headers: new Map(), + } as unknown as Response); + }; + + it('homepageController should return homepage data', async () => { + mockSuccess(mockHtmlData.homepage); + const result = (await homepageController()) as unknown as Record; + expect(result.spotlight).toBeDefined(); + }); + + it('detailpageController should return anime details', async () => { + mockSuccess(mockHtmlData.detail); + const result = await detailpageController(createMockContext({ id: '123' })); + expect(result.title).toBe('Detail Anime'); + }); + + it('searchController should return search results', async () => { + mockSuccess(mockHtmlData.search); + const result = await searchController(createMockContext({}, { keyword: 'one' })); + expect(result.response).toHaveLength(1); + }); + + it('episodesController should return episodes', async () => { + mockSuccess(mockHtmlData.episodes); + const result = await episodesController(createMockContext({ id: '123' })); + expect(result).toHaveLength(1); + }); + + it('charactersController should return characters', async () => { + mockSuccess(mockHtmlData.characters); + const result = await charactersController(createMockContext({ id: '123' })); + expect(result.response).toBeDefined(); + }); + + it('characterDetailController should return character details', async () => { + mockSuccess(mockHtmlData.characterDetail); + const result = await characterDetailController(createMockContext({ id: '123' })); + expect(result.name).toBe('Character Full Name'); + }); + + it('listpageController should return anime list', async () => { + mockSuccess(mockHtmlData.search); + const result = await listpageController(createMockContext({ query: 'most-popular' })); + expect(result.response).toBeDefined(); + }); + + it('topSearchController should return top search items', async () => { + mockSuccess(mockHtmlData.topSearch); + const result = await topSearchController(createMockContext()); + expect(result).toHaveLength(3); + }); + + it('schedulesController should return schedules', async () => { + mockSuccess(JSON.stringify({ html: mockHtmlData.schedule })); + const result = await schedulesController(createMockContext()); + expect(result.data).toBeDefined(); + }); + + it('newsController should return news items', async () => { + mockSuccess(mockHtmlData.news); + const result = await newsController(createMockContext()); + expect(result.news).toHaveLength(1); + }); + + it('suggestionController should return suggestions', async () => { + mockSuccess(JSON.stringify({ html: mockHtmlData.suggestions })); + const result = await suggestionController(createMockContext({}, { keyword: 'suggest' })); + expect(result).toHaveLength(1); + }); + + it('nextEpisodeScheduleController should return next episode time', async () => { + mockSuccess(mockHtmlData.scheduleNext); + const result = await nextEpisodeScheduleController(createMockContext({ id: '123' })); + expect(result).toBe('10:00'); + }); + + it('filterController should handle complex queries', async () => { + mockSuccess(mockHtmlData.search); + const result = await filterController(createMockContext({}, { keyword: 'one' })); + expect(result.response).toBeDefined(); + }); + + it('allGenresController should return all genres', async () => { + mockSuccess(mockHtmlData.homepage); + const result = await allGenresController(); + expect(result).toBeDefined(); + }); + + it('randomController should return random anime', async () => { + mockSuccess(mockHtmlData.search); + const result = await randomController(createMockContext()); + expect(result).toBeDefined(); + }); +}); diff --git a/scripts/tests/jest/extractors/comprehensive.test.ts b/scripts/tests/jest/extractors/comprehensive.test.ts new file mode 100644 index 0000000..a916f69 --- /dev/null +++ b/scripts/tests/jest/extractors/comprehensive.test.ts @@ -0,0 +1,101 @@ +import { describe, it, expect } from '@jest/globals'; +import { extractHomepage } from '../../../../src/extractor/extractHomepage'; +import { extractDetailpage } from '../../../../src/extractor/extractDetailpage'; +import { extractListPage } from '../../../../src/extractor/extractListpage'; +import { extractCharacters } from '../../../../src/extractor/extractCharacters'; +import { extractNews } from '../../../../src/extractor/extractNews'; +import { extractSchedule } from '../../../../src/extractor/extractSchedule'; +import { extractEpisodes } from '../../../../src/extractor/extractEpisodes'; +import { extractCharacterDetail } from '../../../../src/extractor/extractCharacterDetail'; +import { extractSuggestions } from '../../../../src/extractor/extractSuggestions'; +import { extractTopSearch } from '../../../../src/extractor/extractTopSearch'; +import { extractNextEpisodeSchedule } from '../../../../src/extractor/extractNextEpisodeSchedule'; +import { mockHtmlData } from '../../data/mocks'; + +describe('Extractors Comprehensive Suite (Jest)', () => { + describe('extractHomepage', () => { + it('should extract spotlight items', () => { + const result = extractHomepage(mockHtmlData.homepage); + expect(result.spotlight).toHaveLength(1); + expect(result.spotlight[0].title).toBe('Spotlight Anime'); + }); + }); + + describe('extractDetailpage', () => { + it('should extract detail info', () => { + const result = extractDetailpage(mockHtmlData.detail); + expect(result.title).toBe('Detail Anime'); + expect(result.is18Plus).toBe(true); + }); + }); + + describe('extractListPage', () => { + it('should extract results from list page', () => { + const result = extractListPage(mockHtmlData.search); + expect(result.response).toHaveLength(1); + expect(result.response[0].title).toBe('Search Result'); + }); + }); + + describe('extractCharacters', () => { + it('should extract characters', () => { + const result = extractCharacters(mockHtmlData.characters); + expect(result.response).toHaveLength(1); + expect(result.response[0].name).toBe('Character Name'); + }); + }); + + describe('extractNews', () => { + it('should extract news items', () => { + const result = extractNews(mockHtmlData.news); + expect(result.news).toHaveLength(1); + expect(result.news[0].title).toBe('News Title'); + }); + }); + + describe('extractSchedule', () => { + it('should extract schedule', () => { + const result = extractSchedule(mockHtmlData.schedule); + expect(result).toHaveLength(1); + expect(result[0].title).toBe('Scheduled Anime'); + }); + }); + + describe('extractEpisodes', () => { + it('should extract episodes', () => { + const result = extractEpisodes(mockHtmlData.episodes); + expect(result).toHaveLength(1); + expect(result[0].title).toBe('Episode 1'); + }); + }); + + describe('extractCharacterDetail', () => { + it('should extract character detail', () => { + const result = extractCharacterDetail(mockHtmlData.characterDetail); + expect(result.name).toBe('Character Full Name'); + }); + }); + + describe('extractSuggestions', () => { + it('should extract suggestions', () => { + const result = extractSuggestions(mockHtmlData.suggestions); + expect(result).toHaveLength(1); + expect(result[0].title).toBe('S1'); + }); + }); + + describe('extractTopSearch', () => { + it('should extract top search', () => { + const result = extractTopSearch(mockHtmlData.topSearch); + expect(result).toHaveLength(3); + expect(result[2].title).toBe('Top Title'); + }); + }); + + describe('extractNextEpisodeSchedule', () => { + it('should extract next episode schedule', () => { + const result = extractNextEpisodeSchedule(mockHtmlData.scheduleNext); + expect(result).toBe('10:00'); + }); + }); +}); diff --git a/scripts/tests/vitest/controllers/comprehensive.test.ts b/scripts/tests/vitest/controllers/comprehensive.test.ts new file mode 100644 index 0000000..a49f685 --- /dev/null +++ b/scripts/tests/vitest/controllers/comprehensive.test.ts @@ -0,0 +1,137 @@ +import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; +import { Context } from 'hono'; +import homepageController from '../../../../src/controllers/homepage.controller'; +import detailpageController from '../../../../src/controllers/detailpage.controller'; +import searchController from '../../../../src/controllers/search.controller'; +import episodesController from '../../../../src/controllers/episodes.controller'; +import charactersController from '../../../../src/controllers/characters.controller'; +import characterDetailController from '../../../../src/controllers/characterDetail.controller'; +import listpageController from '../../../../src/controllers/listpage.controller'; +import topSearchController from '../../../../src/controllers/topSearch.controller'; +import schedulesController from '../../../../src/controllers/schedules.controller'; +import newsController from '../../../../src/controllers/news.controller'; +import suggestionController from '../../../../src/controllers/suggestion.controller'; +import nextEpisodeScheduleController from '../../../../src/controllers/nextEpisodeSchedule.controller'; +import randomController from '../../../../src/controllers/random.controller'; +import filterController from '../../../../src/controllers/filter.controller'; +import allGenresController from '../../../../src/controllers/allGenres.controller'; +import { mockHtmlData } from '../../data/mocks'; + +// Mock axiosInstance globally +vi.mock('../../../../src/services/axiosInstance', () => ({ + axiosInstance: vi.fn(), +})); + +import { axiosInstance } from '../../../../src/services/axiosInstance'; + +const createMockContext = ( + params: Record = {}, + query: Record = {} +) => { + return { + req: { + param: (name?: string) => (name ? params[name] : params), + query: (name?: string) => (name ? query[name] : query), + }, + json: vi.fn(data => data), + } as unknown as Context; +}; + +describe('Controllers Comprehensive Suite', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + const mockSuccess = (data: string) => + (axiosInstance as Mock).mockResolvedValue({ success: true, data }); + + it('homepageController should return homepage data', async () => { + mockSuccess(mockHtmlData.homepage); + const result = (await homepageController()) as unknown as Record; + expect(result.spotlight).toBeDefined(); + }); + + it('detailpageController should return anime details', async () => { + mockSuccess(mockHtmlData.detail); + const result = await detailpageController(createMockContext({ id: '123' })); + expect(result.title).toBe('Detail Anime'); + }); + + it('searchController should return search results', async () => { + mockSuccess(mockHtmlData.search); + const result = await searchController(createMockContext({}, { keyword: 'one' })); + expect(result.response).toHaveLength(1); + }); + + it('episodesController should return episodes', async () => { + mockSuccess(mockHtmlData.episodes); + const result = await episodesController(createMockContext({ id: '123' })); + expect(result).toHaveLength(1); + }); + + it('charactersController should return characters', async () => { + mockSuccess(mockHtmlData.characters); + const result = await charactersController(createMockContext({ id: '123' })); + expect(result.response).toBeDefined(); + }); + + it('characterDetailController should return character details', async () => { + mockSuccess(mockHtmlData.characterDetail); + const result = await characterDetailController(createMockContext({ id: '123' })); + expect(result.name).toBe('Character Full Name'); + }); + + it('listpageController should return anime list', async () => { + mockSuccess(mockHtmlData.search); + const result = await listpageController(createMockContext({ query: 'most-popular' })); + expect(result.response).toBeDefined(); + }); + + it('topSearchController should return top search items', async () => { + mockSuccess(mockHtmlData.topSearch); + const result = await topSearchController(createMockContext()); + expect(result).toHaveLength(3); + }); + + it('schedulesController should return schedules', async () => { + mockSuccess(JSON.stringify({ html: mockHtmlData.schedule })); + const result = await schedulesController(createMockContext()); + expect(result.data).toBeDefined(); + }); + + it('newsController should return news items', async () => { + mockSuccess(mockHtmlData.news); + const result = await newsController(createMockContext()); + expect(result.news).toHaveLength(1); + }); + + it('suggestionController should return suggestions', async () => { + mockSuccess(JSON.stringify({ html: mockHtmlData.suggestions })); + const result = await suggestionController(createMockContext({}, { keyword: 'suggest' })); + expect(result).toHaveLength(1); + }); + + it('nextEpisodeScheduleController should return next episode time', async () => { + mockSuccess(mockHtmlData.scheduleNext); + const result = await nextEpisodeScheduleController(createMockContext({ id: '123' })); + expect(result).toBe('10:00'); + }); + + it('filterController should handle complex queries', async () => { + mockSuccess(mockHtmlData.search); + const result = await filterController(createMockContext({}, { keyword: 'one' })); + expect(result.response).toBeDefined(); + }); + + it('allGenresController should return all genres', async () => { + mockSuccess(mockHtmlData.homepage); + const result = await allGenresController(); + expect(result).toBeDefined(); + }); + + it('randomController should return random anime', async () => { + mockSuccess(mockHtmlData.search); + const result = await randomController(createMockContext()); + expect(result).toBeDefined(); + }); +}); diff --git a/scripts/tests/vitest/extractors/comprehensive.test.ts b/scripts/tests/vitest/extractors/comprehensive.test.ts new file mode 100644 index 0000000..be82ace --- /dev/null +++ b/scripts/tests/vitest/extractors/comprehensive.test.ts @@ -0,0 +1,101 @@ +import { describe, it, expect } from 'vitest'; +import { extractHomepage } from '../../../../src/extractor/extractHomepage'; +import { extractDetailpage } from '../../../../src/extractor/extractDetailpage'; +import { extractListPage } from '../../../../src/extractor/extractListpage'; +import { extractCharacters } from '../../../../src/extractor/extractCharacters'; +import { extractNews } from '../../../../src/extractor/extractNews'; +import { extractSchedule } from '../../../../src/extractor/extractSchedule'; +import { extractEpisodes } from '../../../../src/extractor/extractEpisodes'; +import { extractCharacterDetail } from '../../../../src/extractor/extractCharacterDetail'; +import { extractSuggestions } from '../../../../src/extractor/extractSuggestions'; +import { extractTopSearch } from '../../../../src/extractor/extractTopSearch'; +import { extractNextEpisodeSchedule } from '../../../../src/extractor/extractNextEpisodeSchedule'; +import { mockHtmlData } from '../../data/mocks'; + +describe('Extractors Comprehensive Suite', () => { + describe('extractHomepage', () => { + it('should extract spotlight items', () => { + const result = extractHomepage(mockHtmlData.homepage); + expect(result.spotlight).toHaveLength(1); + expect(result.spotlight[0].title).toBe('Spotlight Anime'); + }); + }); + + describe('extractDetailpage', () => { + it('should extract detail info', () => { + const result = extractDetailpage(mockHtmlData.detail); + expect(result.title).toBe('Detail Anime'); + expect(result.is18Plus).toBe(true); + }); + }); + + describe('extractListPage', () => { + it('should extract results from list page', () => { + const result = extractListPage(mockHtmlData.search); + expect(result.response).toHaveLength(1); + expect(result.response[0].title).toBe('Search Result'); + }); + }); + + describe('extractCharacters', () => { + it('should extract characters', () => { + const result = extractCharacters(mockHtmlData.characters); + expect(result.response).toHaveLength(1); + expect(result.response[0].name).toBe('Character Name'); + }); + }); + + describe('extractNews', () => { + it('should extract news items', () => { + const result = extractNews(mockHtmlData.news); + expect(result.news).toHaveLength(1); + expect(result.news[0].title).toBe('News Title'); + }); + }); + + describe('extractSchedule', () => { + it('should extract schedule', () => { + const result = extractSchedule(mockHtmlData.schedule); + expect(result).toHaveLength(1); + expect(result[0].title).toBe('Scheduled Anime'); + }); + }); + + describe('extractEpisodes', () => { + it('should extract episodes', () => { + const result = extractEpisodes(mockHtmlData.episodes); + expect(result).toHaveLength(1); + expect(result[0].title).toBe('Episode 1'); + }); + }); + + describe('extractCharacterDetail', () => { + it('should extract character detail', () => { + const result = extractCharacterDetail(mockHtmlData.characterDetail); + expect(result.name).toBe('Character Full Name'); + }); + }); + + describe('extractSuggestions', () => { + it('should extract suggestions', () => { + const result = extractSuggestions(mockHtmlData.suggestions); + expect(result).toHaveLength(1); + expect(result[0].title).toBe('S1'); + }); + }); + + describe('extractTopSearch', () => { + it('should extract top search', () => { + const result = extractTopSearch(mockHtmlData.topSearch); + expect(result).toHaveLength(3); + expect(result[2].title).toBe('Top Title'); + }); + }); + + describe('extractNextEpisodeSchedule', () => { + it('should extract next episode schedule', () => { + const result = extractNextEpisodeSchedule(mockHtmlData.scheduleNext); + expect(result).toBe('10:00'); + }); + }); +}); diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..dccf760 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,62 @@ +import { Hono, Context } from 'hono'; +import { cors } from 'hono/cors'; +import hiAnimeRoutes from './routes/routes'; +import { AppError } from './utils/errors'; +import { fail } from './utils/response'; +import { logger } from 'hono/logger'; +import config from './config/config'; + +const app = new Hono(); +const origins = config.origin.includes(',') + ? config.origin.split(',').map(o => o.trim()) + : config.origin === '*' + ? '*' + : [config.origin]; + +app.use( + '*', + cors({ + origin: origins, + allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'], + exposeHeaders: ['Content-Length', 'X-Request-Id'], + maxAge: 600, + credentials: true, + }) +); + +if (!config.isProduction || config.enableLogging) { + app.use('/api/v2/*', logger()); +} + +app.get('/ping', (c: Context) => { + return c.json({ + status: 'ok', + timestamp: new Date().toISOString(), + environment: config.isVercel ? 'vercel' : 'self-hosted', + }); +}); + +app.get('/favicon.ico', (c: Context) => { + return c.body(null, 204); +}); + +app.route('/api/v2', hiAnimeRoutes); +app.onError((err, c) => { + if (err instanceof AppError) { + return fail(c, err.message, err.statusCode, err.details); + } + + console.error('Unexpected Error:', err.message); + if (!config.isProduction) { + console.error('Stack:', err.stack); + } + + return fail(c, 'Internal server error', 500); +}); + +app.notFound((c: Context) => { + return fail(c, 'Route not found', 404); +}); + +export default app; diff --git a/src/config/config.ts b/src/config/config.ts new file mode 100644 index 0000000..5c697ac --- /dev/null +++ b/src/config/config.ts @@ -0,0 +1,18 @@ +const config = { + baseurl: 'https://hianime.to', + baseurl2: 'https://aniwatchtv.to', + origin: '*', + port: 5000, + + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0', + }, + + logLevel: 'INFO', + enableLogging: false, + isProduction: true, + isDevelopment: false, + isVercel: false, +}; + +export default config; diff --git a/src/config/dataUrl.ts b/src/config/dataUrl.ts new file mode 100644 index 0000000..e156d5b --- /dev/null +++ b/src/config/dataUrl.ts @@ -0,0 +1,2 @@ +export const dataURL = + 'data:image/png;base64,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JDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAtwCFrnv+pEcAAQQQECJAoQsZJDEQQAABBHQLUOi65096BBBAAAEhAhS6kEESAwEEEEBAt8D/ATY93seMmImHAAAAAElFTkSuQmCC'; diff --git a/src/controllers/allGenres.controller.ts b/src/controllers/allGenres.controller.ts new file mode 100644 index 0000000..072d468 --- /dev/null +++ b/src/controllers/allGenres.controller.ts @@ -0,0 +1,48 @@ +const allGenres: string[] = [ + 'action', + 'adventure', + 'cars', + 'comedy', + 'dementia', + 'demons', + 'drama', + 'ecchi', + 'fantasy', + 'game', + 'harem', + 'historical', + 'horror', + 'isekai', + 'josei', + 'kids', + 'magic', + 'martial arts', + 'mecha', + 'military', + 'music', + 'mystery', + 'parody', + 'police', + 'psychological', + 'romance', + 'samurai', + 'school', + 'sci-fi', + 'seinen', + 'shoujo', + 'shoujo ai', + 'shounen', + 'shounen ai', + 'slice of life', + 'space', + 'sports', + 'super power', + 'supernatural', + 'thriller', + 'vampire', +]; +const allGenresController = (): string[] => { + return allGenres; +}; + +export default allGenresController; diff --git a/src/controllers/characterDetail.controller.ts b/src/controllers/characterDetail.controller.ts new file mode 100644 index 0000000..b148f51 --- /dev/null +++ b/src/controllers/characterDetail.controller.ts @@ -0,0 +1,21 @@ +import { Context } from 'hono'; +import { extractCharacterDetail, CharacterDetail } from '../extractor/extractCharacterDetail'; +import { axiosInstance } from '../services/axiosInstance'; +import { validationError } from '../utils/errors'; + +const characterDetailConroller = async (c: Context): Promise => { + const id = c.req.param('id'); + + if (!id) throw new validationError('id is required'); + + const result = await axiosInstance(`/${id.replace(':', '/')}`); + if (!result.success || !result.data) { + throw new validationError(result.message || 'make sure given endpoint is correct'); + } + + const response = extractCharacterDetail(result.data); + + return response; +}; + +export default characterDetailConroller; diff --git a/src/controllers/characters.controller.ts b/src/controllers/characters.controller.ts new file mode 100644 index 0000000..8cc4c84 --- /dev/null +++ b/src/controllers/characters.controller.ts @@ -0,0 +1,39 @@ +import { Context } from 'hono'; +import config from '../config/config'; +import { validationError } from '../utils/errors'; +import { extractCharacters, CharactersResponse } from '../extractor/extractCharacters'; +import { axiosInstance } from '../services/axiosInstance'; + +const charactersController = async (c: Context): Promise => { + try { + const id = c.req.param('id'); + const page = c.req.query('page') || '1'; + + if (!id) throw new validationError('id is required'); + + const idNum = id.split('-').pop(); + const endpoint = `/ajax/character/list/${idNum}?page=${page}`; + + const result = await axiosInstance(endpoint, { + headers: { Referer: `${config.baseurl}/home` }, + }); + + if (!result.success || !result.data) { + throw new validationError(result.message || 'characters not found'); + } + + const response = extractCharacters(result.data); + + return response; + } catch (err: unknown) { + if (err instanceof Error) { + console.log(err.message); + } else { + console.log(err); + } + + throw new validationError('characters not found'); + } +}; + +export default charactersController; diff --git a/src/controllers/detailpage.controller.ts b/src/controllers/detailpage.controller.ts new file mode 100644 index 0000000..cbcdb73 --- /dev/null +++ b/src/controllers/detailpage.controller.ts @@ -0,0 +1,20 @@ +import { Context } from 'hono'; +import { extractDetailpage } from '../extractor/extractDetailpage'; +import { axiosInstance } from '../services/axiosInstance'; +import { validationError } from '../utils/errors'; +import { DetailAnime } from '../types/anime'; + +const detailpageController = async (c: Context): Promise => { + const id = c.req.param('id'); + + const result = await axiosInstance(`/${id}`); + if (!result.success || !result.data) { + throw new validationError( + result.message || 'Failed to fetch detail page', + 'maybe id is incorrect : ' + id + ); + } + return extractDetailpage(result.data); +}; + +export default detailpageController; diff --git a/src/controllers/episodes.controller.ts b/src/controllers/episodes.controller.ts new file mode 100644 index 0000000..6f3788f --- /dev/null +++ b/src/controllers/episodes.controller.ts @@ -0,0 +1,29 @@ +import { Context } from 'hono'; +import config from '../config/config'; +import { validationError } from '../utils/errors'; +import { extractEpisodes, Episode } from '../extractor/extractEpisodes'; +import { axiosInstance } from '../services/axiosInstance'; + +const episodesController = async (c: Context): Promise => { + const id = c.req.param('id'); + + if (!id) throw new validationError('id is required'); + + const idNum = id.split('-').at(-1); + const ajaxUrl = `/ajax/v2/episode/list/${idNum}`; + + const result = await axiosInstance(ajaxUrl, { + headers: { Referer: `${config.baseurl}/watch/${id}` }, + }); + + if (!result.success || !result.data) { + throw new validationError(result.message || 'make sure the id is correct', { + validIdEX: 'one-piece-100', + }); + } + + const response = extractEpisodes(result.data); + return response; +}; + +export default episodesController; diff --git a/src/controllers/filter.controller.ts b/src/controllers/filter.controller.ts new file mode 100644 index 0000000..d88bb8d --- /dev/null +++ b/src/controllers/filter.controller.ts @@ -0,0 +1,106 @@ +import { Context } from 'hono'; +import filterOptions from '../utils/filter'; +import { axiosInstance } from '../services/axiosInstance'; +import { validationError } from '../utils/errors'; +import { extractListPage, ListPageResponse } from '../extractor/extractListpage'; + +const filterController = async (c: Context): Promise => { + const { + // will receive string and send as a string + keyword = null, + sort = null, + + // will recieve an array as string will "," saparated and send as "," saparated string + genres = null, + + // will recieve as string and send as index of that string "see filterOptions" + type = null, + status = null, + rated = null, + score = null, + season = null, + language = null, + page = '1', + } = c.req.query(); + + const pageNum = Number(page); + const queryArr = [ + { title: 'keyword', val: keyword }, + { title: 'sort', val: sort }, + { title: 'type', val: type }, + { title: 'status', val: status }, + { title: 'rated', val: rated }, + { title: 'score', val: score }, + { title: 'season', val: season }, + { title: 'language', val: language }, + { title: 'genres', val: genres }, + ]; + + const params = new URLSearchParams(); + + queryArr.forEach(v => { + if (v.val) { + switch (v.title) { + case 'keyword': + params.set('keyword', formatKeyword(v.val)); + break; + case 'genres': + params.set('genres', formatGenres(v.val)); + break; + case 'sort': { + const formattedSort = formatSort(v.val); + if (formattedSort) params.set('sort', formattedSort); + break; + } + default: { + const formattedOption = formatOption(v.title, v.val); + if (formattedOption) params.set(v.title, formattedOption); + } + } + } + }); + + if (pageNum > 1) params.set('page', String(pageNum)); + + const endpoint = keyword ? '/search' : '/filter'; + const queryString = params.toString(); + const url = queryString ? `${endpoint}?${queryString}` : endpoint; + + const result = await axiosInstance(url); + + console.log(result.message); + + if (!result.success || !result.data) + throw new validationError(result.message || 'something went wrong will queries'); + const response = extractListPage(result.data); + return response; +}; + +const formatKeyword = (v: string) => v.toLowerCase(); + +const formatSort = (v: string) => { + const index = filterOptions.sort.indexOf(v.toLowerCase().replace(' ', '_')); + if (index === -1) return null; + return filterOptions.sort[index]; +}; + +const formatGenres = (v: string) => { + let indexes = v + .split(',') + .map(genre => + (filterOptions as Record).genres.indexOf( + genre.toLowerCase().replaceAll(' ', '_') + ) + ) + .filter(i => i !== -1) + .map(i => i + 1); + + return indexes.length > 0 ? indexes.join(',') : ''; +}; + +const formatOption = (k: string, v: string) => { + const index = (filterOptions as Record)[k].indexOf(v); + if (index === -1) return null; + return index.toString(); +}; +export default filterController; diff --git a/src/controllers/homepage.controller.ts b/src/controllers/homepage.controller.ts new file mode 100644 index 0000000..0b2394b --- /dev/null +++ b/src/controllers/homepage.controller.ts @@ -0,0 +1,18 @@ +import { axiosInstance } from '../services/axiosInstance'; +import { validationError } from '../utils/errors'; +import { extractHomepage } from '../extractor/extractHomepage'; +import { HomePage } from '../types/anime'; + +const homepageController = async (): Promise => { + console.log('Fetching homepage data from external API...'); + const result = await axiosInstance('/home'); + + if (!result.success || !result.data) { + console.error('Homepage fetch failed:', result.message); + throw new validationError(result.message || 'Failed to fetch homepage'); + } + + return extractHomepage(result.data); +}; + +export default homepageController; diff --git a/src/controllers/listpage.controller.ts b/src/controllers/listpage.controller.ts new file mode 100644 index 0000000..f057294 --- /dev/null +++ b/src/controllers/listpage.controller.ts @@ -0,0 +1,60 @@ +import { Context } from 'hono'; +import { extractListPage, ListPageResponse } from '../extractor/extractListpage'; +import { axiosInstance } from '../services/axiosInstance'; +import { NotFoundError, validationError } from '../utils/errors'; + +const listpageController = async (c: Context): Promise => { + const validateQueries = [ + 'top-airing', + 'most-popular', + 'most-favorite', + 'completed', + 'recently-added', + 'recently-updated', + 'top-upcoming', + 'genre', + 'producer', + 'az-list', + 'subbed-anime', + 'dubbed-anime', + 'movie', + 'tv', + 'ova', + 'ona', + 'special', + 'events', + ]; + const query = c.req.param('query')?.toLowerCase() || ''; + + if (!validateQueries.includes(query)) + throw new validationError('invalid query', { validateQueries }); + + let category = c.req.param('category') || null; + + const page = c.req.query('page') || '1'; + + if ((query === 'genre' || query === 'producer') && !category) { + throw new validationError(`category is require for query ${query}`); + } + if (query !== 'genre' && query !== 'producer' && query !== 'az-list' && category) { + category = null; + } + + let nromalizeCategory = category && category.replaceAll(' ', '-').toLowerCase(); + if (nromalizeCategory === 'martial-arts') nromalizeCategory = 'marial-arts'; + const endpoint = category + ? `/${query}/${nromalizeCategory}?page=${page}` + : `/${query}?page=${page}`; + + const result = await axiosInstance(endpoint); + + if (!result.success || !result.data) { + throw new validationError(result.message || 'make sure given endpoint is correct'); + } + const response = extractListPage(result.data); + + if (response.response.length < 1) throw new NotFoundError(); + return response; +}; + +export default listpageController; diff --git a/src/controllers/news.controller.ts b/src/controllers/news.controller.ts new file mode 100644 index 0000000..8c98257 --- /dev/null +++ b/src/controllers/news.controller.ts @@ -0,0 +1,21 @@ +import { Context } from 'hono'; +import { axiosInstance } from '../services/axiosInstance'; +import { validationError } from '../utils/errors'; +import { extractNews, NewsResponse } from '../extractor/extractNews'; + +const newsController = async (c: Context): Promise => { + const page = c.req.query('page') || '1'; + + console.log(`Fetching news page ${page} from external API...`); + const endpoint = page === '1' ? '/news' : `/news?page=${page}`; + const result = await axiosInstance(endpoint); + + if (!result.success || !result.data) { + console.error('News fetch failed:', result.message); + throw new validationError(result.message || 'Failed to fetch news'); + } + + return extractNews(result.data); +}; + +export default newsController; diff --git a/src/controllers/nextEpisodeSchedule.controller.ts b/src/controllers/nextEpisodeSchedule.controller.ts new file mode 100644 index 0000000..80383e7 --- /dev/null +++ b/src/controllers/nextEpisodeSchedule.controller.ts @@ -0,0 +1,21 @@ +import { Context } from 'hono'; +import { extractNextEpisodeSchedule } from '../extractor/extractNextEpisodeSchedule'; +import { axiosInstance } from '../services/axiosInstance'; +import { validationError } from '../utils/errors'; + +const nextEpisodeSchaduleController = async (c: Context): Promise => { + const id = c.req.param('id'); + + if (!id) throw new validationError('id is required'); + + const data = await axiosInstance('/watch/' + id); + + if (!data.success || !data.data) + throw new validationError(data.message || 'make sure id is correct'); + + const response = extractNextEpisodeSchedule(data.data); + + return response; +}; + +export default nextEpisodeSchaduleController; diff --git a/src/controllers/random.controller.ts b/src/controllers/random.controller.ts new file mode 100644 index 0000000..8f10322 --- /dev/null +++ b/src/controllers/random.controller.ts @@ -0,0 +1,33 @@ +import { Context } from 'hono'; +import { axiosInstance } from '../services/axiosInstance'; +import { validationError } from '../utils/errors'; +import * as cheerio from 'cheerio'; + +const randomController = async (_c: Context): Promise<{ id: string }> => { + console.log('Fetching random anime...'); + const result = await axiosInstance('/home'); + + if (!result.success || !result.data) { + console.error('Random anime fetch failed:', result.message); + throw new validationError(result.message || 'Failed to fetch homepage for random selection'); + } + + const $ = cheerio.load(result.data); + + const animes: string[] = []; + $('.flw-item').each((i, el) => { + const link = $(el).find('.film-name .dynamic-name').attr('href'); + const id = link?.split('/').pop(); + if (id) animes.push(id); + }); + + if (animes.length === 0) { + throw new validationError('No anime found'); + } + + const randomId = animes[Math.floor(Math.random() * animes.length)]; + + return { id: randomId }; +}; + +export default randomController; diff --git a/src/controllers/schedules.controller.ts b/src/controllers/schedules.controller.ts new file mode 100644 index 0000000..67f13f1 --- /dev/null +++ b/src/controllers/schedules.controller.ts @@ -0,0 +1,84 @@ +import { Context } from 'hono'; +import config from '../config/config'; +import { validationError } from '../utils/errors'; +import { extractSchedule, ScheduledAnime } from '../extractor/extractSchedule'; +import { axiosInstance } from '../services/axiosInstance'; + +export interface ScheduleResponse { + success: boolean; + data: { + [date: string]: ScheduledAnime[]; + }; +} + +async function schedulesController(c: Context): Promise { + const today = new Date(); + const dateParam = c.req.query('date'); + + let startDate = today; + if (dateParam) { + const [year, month, day] = dateParam.split('-').map(Number); + startDate = new Date(year, month - 1, day); + if (isNaN(startDate.getTime())) { + throw new validationError('Invalid date format. Use YYYY-MM-DD'); + } + } + + const dates: string[] = []; + for (let i = 0; i < 7; i++) { + const d = new Date(startDate); + d.setDate(startDate.getDate() + i); + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + dates.push(`${year}-${month}-${day}`); + } + + try { + const promises = dates.map(async date => { + const ajaxUrl = `/ajax/schedule/list?tzOffset=-330&date=${date}`; + try { + const result = await axiosInstance(ajaxUrl, { + headers: { Referer: `${config.baseurl}/home` }, + }); + + if (!result.success || !result.data) { + throw new Error(result.message || 'Failed to fetch'); + } + + const jsonData = JSON.parse(result.data); + return { + date, + shows: extractSchedule(jsonData.html), + }; + } catch (err: unknown) { + const errorMessage = err instanceof Error ? err.message : 'Unknown error'; + console.error(`Failed to fetch schedule for ${date}: ${errorMessage}`); + return { + date, + shows: [] as ScheduledAnime[], + error: 'Failed to fetch', + }; + } + }); + + const results = await Promise.all(promises); + + // Format response to map dates to shows + const response: { [date: string]: ScheduledAnime[] } = {}; + results.forEach(result => { + response[result.date] = result.shows; + }); + + return { + success: true, + data: response, + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + console.error(errorMessage); + throw new validationError('Failed to fetch schedules'); + } +} + +export default schedulesController; diff --git a/src/controllers/search.controller.ts b/src/controllers/search.controller.ts new file mode 100644 index 0000000..f1480a3 --- /dev/null +++ b/src/controllers/search.controller.ts @@ -0,0 +1,30 @@ +import { Context } from 'hono'; +import { extractListPage, ListPageResponse } from '../extractor/extractListpage'; +import { axiosInstance } from '../services/axiosInstance'; +import { NotFoundError, validationError } from '../utils/errors'; + +const searchController = async (c: Context): Promise => { + const keyword = c.req.query('keyword') || null; + const page = c.req.query('page') || '1'; + + if (!keyword) throw new validationError('query is required'); + + const noSpaceKeyword = keyword.trim().toLowerCase().replace(/\s+/g, '+'); + + const endpoint = `/search?keyword=${noSpaceKeyword}&page=${page}`; + const result = await axiosInstance(endpoint); + + if (!result.success || !result.data) { + throw new validationError(result.message || 'make sure given endpoint is correct'); + } + + const response = extractListPage(result.data); + + if (response.response.length < 1) { + throw new NotFoundError('page not found'); + } + + return response; +}; + +export default searchController; diff --git a/src/controllers/suggestion.controller.ts b/src/controllers/suggestion.controller.ts new file mode 100644 index 0000000..d9bbfb3 --- /dev/null +++ b/src/controllers/suggestion.controller.ts @@ -0,0 +1,32 @@ +import { Context } from 'hono'; +import config from '../config/config'; +import { validationError } from '../utils/errors'; +import { extractSuggestions, Suggestion } from '../extractor/extractSuggestions'; +import { axiosInstance } from '../services/axiosInstance'; + +const suggestionController = async (c: Context): Promise => { + const keyword = c.req.query('keyword') || null; + + if (!keyword) throw new validationError('query is required'); + + const noSpaceKeyword = keyword.trim().toLowerCase().replace(/\s+/g, '+'); + const endpoint = `/ajax/search/suggest?keyword=${noSpaceKeyword}`; + + const result = await axiosInstance(endpoint, { + headers: { Referer: `${config.baseurl}/home` }, + }); + + if (!result.success || !result.data) { + throw new validationError(result.message || 'suggestion not found'); + } + + // Parse HTML from JSON if necessary, or just use result.data if it's already HTML + // In hianime API, suggestion ajax usually returns JSON with { status, html } + // but my axiosInstance returns response.text() which is the raw JSON string. + const jsonData = JSON.parse(result.data); + const response = extractSuggestions(jsonData.html); + + return response; +}; + +export default suggestionController; diff --git a/src/controllers/topSearch.controller.ts b/src/controllers/topSearch.controller.ts new file mode 100644 index 0000000..8dd6816 --- /dev/null +++ b/src/controllers/topSearch.controller.ts @@ -0,0 +1,18 @@ +import { Context } from 'hono'; +import { axiosInstance } from '../services/axiosInstance'; +import { validationError } from '../utils/errors'; +import { extractTopSearch, TopSearchAnime } from '../extractor/extractTopSearch'; + +const topSearchController = async (_c: Context): Promise => { + console.log('Fetching top search data from external API...'); + const result = await axiosInstance('/'); + + if (!result.success || !result.data) { + console.error('Top search fetch failed:', result.message); + throw new validationError(result.message || 'Failed to fetch top search'); + } + + return extractTopSearch(result.data); +}; + +export default topSearchController; diff --git a/src/extractor/extractCharacterDetail.ts b/src/extractor/extractCharacterDetail.ts new file mode 100644 index 0000000..6dbb1e8 --- /dev/null +++ b/src/extractor/extractCharacterDetail.ts @@ -0,0 +1,147 @@ +import { load } from 'cheerio'; + +export interface AnimeAppearance { + title: string | null; + alternativeTitle: string | null; + id: string | null; + poster: string | null; + role: string | null; + type: string | null; +} + +export interface VoiceActorShort { + name: string | null; + imageUrl: string | null; + id: string | null; + language: string | null; +} + +export interface VoiceActingRole { + anime: { + title: string | null; + poster: string | null; + id: string | null; + typeAndYear: string | null; + }; + character: { + name: string | null; + imageUrl: string | null; + id: string | null; + role: string | null; + }; +} + +export interface CharacterDetail { + name: string | null; + type: 'people' | 'character' | string; + japanese: string | null; + imageUrl: string | null; + bio: string | null; + animeAppearances?: AnimeAppearance[]; + voiceActors?: VoiceActorShort[]; + voiceActingRoles?: VoiceActingRole[]; +} + +export const extractCharacterDetail = (html: string): CharacterDetail => { + const $ = load(html); + + const transformId = (id: string | undefined): string | null => { + if (!id) return null; + return id.replace(/^\//, '').replace('/', ':'); + }; + + const whoIsHe = + $('nav .breadcrumb .active').prev().find('a').text() === 'People' ? 'people' : 'character'; + + const obj: CharacterDetail = { + name: null, + type: whoIsHe, + japanese: null, + imageUrl: null, + bio: null, + }; + + obj.imageUrl = $('.actor-page-wrap .avatar img').attr('src') || null; + const allDetails = $('.apw-detail'); + obj.name = allDetails.find('.name').text(); + obj.japanese = allDetails.find('.sub-name').text(); + + obj.bio = allDetails.find('.tab-content #bio .bio').html()?.trim() || null; + + if (whoIsHe === 'character') { + obj.animeAppearances = []; + + allDetails.find('.tab-content #animeography .anif-block-ul .ulclear li').each((i, el) => { + const innerObj: AnimeAppearance = { + title: null, + alternativeTitle: null, + id: null, + poster: null, + role: null, + type: null, + }; + const titleEl = $(el).find('.dynamic-name'); + innerObj.title = titleEl.attr('title') || null; + innerObj.alternativeTitle = titleEl.attr('data-jname') || null; + innerObj.id = transformId(titleEl.attr('href')); + + innerObj.poster = $(el).find('.film-poster .film-poster-img').attr('src') || null; + innerObj.role = $(el).find('.fd-infor .fdi-item').first().text().split(' ').shift() || null; + innerObj.type = $(el).find('.fd-infor .fdi-item').last().text(); + + obj.animeAppearances?.push(innerObj); + }); + + obj.voiceActors = []; + allDetails.find('#voiactor .sub-box-list .per-info').each((i, el) => { + const innerObj: VoiceActorShort = { + name: null, + imageUrl: null, + id: null, + language: null, + }; + innerObj.imageUrl = $(el).find('.pi-avatar img').attr('src') || null; + innerObj.name = $(el).find('.pi-name a').text(); + innerObj.id = transformId($(el).find('.pi-name a').attr('href')); + + innerObj.language = $(el).find('.pi-cast').text(); + + obj.voiceActors?.push(innerObj); + }); + } else { + obj.voiceActingRoles = []; + $('#voice .bac-list-wrap .bac-item').each((i, el) => { + const animeInfo = $(el).find('.per-info.anime-info'); + const characterInfo = $(el).find('.per-info.rtl'); + + const innerObj: VoiceActingRole = { + anime: { + title: null, + poster: null, + id: null, + typeAndYear: null, + }, + character: { + name: null, + imageUrl: null, + id: null, + role: null, + }, + }; + + innerObj.anime.title = animeInfo.find('.pi-name a').text().trim(); + innerObj.anime.id = animeInfo.find('.pi-name a').attr('href')?.split('/').pop() || null; + innerObj.anime.poster = animeInfo.find('.pi-avatar img').attr('src') || null; + innerObj.anime.typeAndYear = animeInfo.find('.pi-cast').text().trim(); + + innerObj.character.name = characterInfo.find('.pi-name a').text().trim(); + innerObj.character.id = transformId(characterInfo.find('.pi-name a').attr('href')); + innerObj.character.imageUrl = characterInfo.find('.pi-avatar img').attr('src') || null; + innerObj.character.role = characterInfo.find('.pi-cast').text().trim(); + + obj.voiceActingRoles?.push(innerObj); + }); + } + + return obj; +}; diff --git a/src/extractor/extractCharacters.ts b/src/extractor/extractCharacters.ts new file mode 100644 index 0000000..8b58252 --- /dev/null +++ b/src/extractor/extractCharacters.ts @@ -0,0 +1,121 @@ +import { load } from 'cheerio'; + +export interface Character { + name: string | null; + id: string | null; + imageUrl: string | null; + role: string | null; + voiceActors: VoiceActor[]; +} + +export interface VoiceActor { + name: string | null; + id: string | null; + imageUrl: string | null; + cast?: string | null; +} + +export interface CharactersResponse { + pageInfo?: { + totalPages: number; + currentPage: number; + hasNextPage: boolean; + }; + response: Character[]; +} + +export const extractCharacters = (html: string): CharactersResponse => { + const $ = load(html); + + const response: Character[] = []; + const paginationEl = $('.pre-pagination .pagination .page-item'); + + let currentPage: number, hasNextPage: boolean, totalPages: number; + if (!paginationEl.length) { + currentPage = 1; + hasNextPage = false; + totalPages = 1; + } else { + currentPage = Number(paginationEl.find('.active .page-link').text()); + hasNextPage = !paginationEl.last().hasClass('active'); + totalPages = hasNextPage + ? Number(paginationEl.last().find('.page-link').attr('data-url')?.split('page=').at(-1)) + : Number(paginationEl.last().find('.page-link').text()); + } + + const pageInfo = { + totalPages, + currentPage, + hasNextPage, + }; + + const characters = $('.bac-item'); + if (!characters.length) return { response }; + $(characters).each((i, el) => { + const obj: Character = { + name: null, + id: null, + imageUrl: null, + role: null, + voiceActors: [], + }; + const characterDetail = $(el).find('.per-info').first(); + const voiceActorsDetail = $(el).find('.per-info-xx').length + ? $(el).find('.per-info-xx') + : $(el).find('.rtl'); + + obj.name = $(characterDetail).find('.pi-detail .pi-name a').text(); + obj.role = $(characterDetail).find('.pi-detail .pi-cast').text(); + obj.id = $(characterDetail).find('.pi-avatar').length + ? $(characterDetail).find('.pi-avatar').attr('href')?.replace(/^\//, '').replace('/', ':') || + null + : null; + obj.imageUrl = $(characterDetail).find('.pi-avatar img').attr('data-src') || null; + + if (!voiceActorsDetail.length) { + response.push(obj); + return; + } + const hasMultiple = $(voiceActorsDetail).hasClass('per-info-xx'); + + if (hasMultiple) { + $(voiceActorsDetail) + .find('.pix-list a') + .each((index, item) => { + const innerObj: VoiceActor = { + name: null, + id: null, + imageUrl: null, + cast: null, + }; + innerObj.name = $(item).attr('title') || null; + innerObj.id = $(item).attr('href')?.replace(/^\//, '').replace('/', ':') || null; + innerObj.imageUrl = $(item).find('img').attr('data-src') || null; + + obj.voiceActors.push(innerObj); + }); + } else { + const innerObj: VoiceActor = { + name: null, + id: null, + imageUrl: null, + cast: null, + }; + innerObj.id = $(voiceActorsDetail).find('.pi-avatar').length + ? $(voiceActorsDetail) + .find('.pi-avatar') + .attr('href') + ?.replace(/^\//, '') + .replace('/', ':') || null + : null; + innerObj.imageUrl = $(voiceActorsDetail).find('.pi-avatar img').attr('data-src') || null; + innerObj.name = $(voiceActorsDetail).find('.pi-avatar img').attr('alt') || null; + innerObj.cast = $(voiceActorsDetail).find('.pi-cast').text(); + + obj.voiceActors.push(innerObj); + } + + response.push(obj); + }); + return { pageInfo, response }; +}; diff --git a/src/extractor/extractDetailpage.ts b/src/extractor/extractDetailpage.ts new file mode 100644 index 0000000..cb21605 --- /dev/null +++ b/src/extractor/extractDetailpage.ts @@ -0,0 +1,251 @@ +import { load } from 'cheerio'; +import { Element } from 'domhandler'; +import { DetailAnime, AnimeFeatured, Season } from '../types/anime'; + +export const extractDetailpage = (html: string): DetailAnime => { + const $ = load(html); + + const obj: DetailAnime = { + title: null, + alternativeTitle: null, + japanese: null, + id: null, + poster: null, + rating: null, + type: null, + is18Plus: false, + episodes: { + sub: null, + dub: null, + eps: null, + }, + synopsis: null, + synonyms: null, + aired: { + from: null, + to: null, + }, + premiered: null, + duration: null, + status: null, + MAL_score: null, + genres: [], + studios: [], + producers: [], + moreSeasons: [], + related: [], + mostPopular: [], + recommended: [], + }; + + const main = $('#ani_detail .anis-content'); + const moreSeasons = $('#main-content .block_area-seasons'); + const relatedAndMostPopular = $('#main-sidebar .block_area'); + const recommended = $( + '.block_area.block_area_category .tab-content .block_area-content .film_list-wrap .flw-item' + ); + + obj.poster = main.find('.film-poster .film-poster-img').attr('src') || null; + obj.is18Plus = Boolean(main.find('.film-poster .tick-rate').length > 0); + + const titleEl = main.find('.anisc-detail .film-name'); + obj.title = titleEl.text(); + obj.alternativeTitle = titleEl.attr('data-jname') || null; + + const info = main.find('.film-stats .tick'); + + obj.rating = info.find('.tick-pg').text(); + obj.episodes.sub = Number(info.find('.tick-sub').text()) || null; + obj.episodes.dub = Number(info.find('.tick-dub').text()) || null; + obj.episodes.eps = info.find('.tick-eps').length + ? Number(info.find('.tick-eps').text()) || null + : Number(info.find('.tick-sub').text()) || null; + + obj.type = info.find('.item').first().text(); + + const idLink = main.find('.film-buttons .btn'); + + obj.id = idLink.length ? idLink.attr('href')?.split('/').at(-1) || null : null; + + const moreInfo = main.find('.anisc-info-wrap .anisc-info .item'); + + moreInfo.each((i: number, el: Element) => { + const name = $(el).find('.item-head').text(); + + switch (name) { + case 'Overview:': + obj.synopsis = $(el).find('.text').text().trim(); + break; + case 'Japanese:': + obj.japanese = $(el).find('.name').text(); + break; + case 'Synonyms:': + obj.synonyms = $(el).find('.name').text(); + break; + case 'Aired:': { + let aired = $(el).find('.name').text().split('to'); + obj.aired.from = aired[0].trim(); + if (aired.length > 1) { + const secondPart = aired[1].trim(); + obj.aired.to = secondPart === '?' ? null : secondPart; + } else { + obj.aired.to = null; + } + + break; + } + case 'Premiered:': + obj.premiered = $(el).find('.name').text(); + break; + case 'Duration:': + obj.duration = $(el).find('.name').text(); + break; + case 'Status:': + obj.status = $(el).find('.name').text(); + break; + case 'MAL Score:': + obj.MAL_score = $(el).find('.name').text(); + break; + case 'Genres:': + obj.genres = $(el) + .find('a') + .map((i: number, genre: Element) => $(genre).text()) + .get(); + break; + case 'Studios:': + obj.studios = $(el) + .find('a') + .map((i: number, studio: Element) => $(studio).attr('href')?.split('/').at(-1)) + .get() as string[]; + break; + case 'Producers:': + obj.producers = $(el) + .find('a') + .map((i: number, producer: Element) => $(producer).attr('href')?.split('/').at(-1)) + .get() as string[]; + break; + default: + break; + } + }); + + if (moreSeasons.length) { + $(moreSeasons) + .find('.os-list .os-item') + .each((i, el) => { + const innerObj: Season = { + title: null, + alternativeTitle: null, + id: null, + poster: null, + isActive: false, + }; + innerObj.title = $(el).attr('title') || null; + innerObj.id = $(el).attr('href')?.split('/').pop() || null; + innerObj.alternativeTitle = $(el).find('.title').text(); + const posterStyle = $(el).find('.season-poster').attr('style'); + + if (posterStyle) { + const match = posterStyle.match(/url\((['"])?(.*?)\1\)/); + innerObj.poster = match ? match[2] : null; + } + + innerObj.isActive = $(el).hasClass('active'); + + obj.moreSeasons.push(innerObj); + }); + } + + const extractRelatedAndMostPopular = (index: number, array: AnimeFeatured[]) => { + relatedAndMostPopular + .eq(index) + .find('.cbox .ulclear li') + .each((i, el) => { + const innerObj: AnimeFeatured = { + title: null, + alternativeTitle: null, + id: null, + poster: null, + type: null, + episodes: { + sub: null, + dub: null, + eps: null, + }, + }; + + const titleEl = $(el).find('.film-name .dynamic-name'); + innerObj.title = titleEl.text(); + innerObj.alternativeTitle = titleEl.attr('data-jname') || null; + innerObj.id = titleEl.attr('href')?.split('/').pop() || null; + + const infor = $(el).find('.fd-infor .tick'); + + innerObj.type = infor + .contents() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .filter((i: number, el: any) => { + return el.type === 'text' && $(el).text().trim() !== ''; + }) + .text() + .trim(); + + innerObj.episodes.sub = Number(infor.find('.tick-sub').text()) || null; + innerObj.episodes.dub = Number(infor.find('.tick-dub').text()) || null; + + const epsEl = infor.find('.tick-eps').length + ? infor.find('.tick-eps').text() + : infor.find('.tick-sub').text(); + + innerObj.episodes.eps = Number(epsEl) || null; + + innerObj.poster = $(el).find('.film-poster .film-poster-img').attr('data-src') || null; + + array.push(innerObj); + }); + }; + if (relatedAndMostPopular.length > 1) { + extractRelatedAndMostPopular(0, obj.related); + extractRelatedAndMostPopular(1, obj.mostPopular); + } else { + extractRelatedAndMostPopular(0, obj.mostPopular); + } + + recommended.each((i: number, el: Element) => { + const innerObj: AnimeFeatured & { is18Plus: boolean; duration: string | null } = { + title: null, + alternativeTitle: null, + id: null, + poster: null, + type: null, + duration: null, + episodes: { + sub: null, + dub: null, + eps: null, + }, + is18Plus: false, + }; + const titleEl = $(el).find('.film-detail .film-name .dynamic-name'); + innerObj.title = titleEl.text(); + innerObj.alternativeTitle = titleEl.attr('data-jname') || null; + innerObj.id = titleEl.attr('href')?.split('/').pop() || null; + innerObj.type = $(el).find('.fd-infor .fdi-item').first().text(); + innerObj.duration = $(el).find('.fd-infor .fdi-duration').text(); + + innerObj.poster = $(el).find('.film-poster .film-poster-img').attr('data-src') || null; + innerObj.is18Plus = $(el).find('.film-poster').has('.tick-rate').length > 0; + + innerObj.episodes.sub = Number($(el).find('.film-poster .tick .tick-sub').text()) || null; + innerObj.episodes.dub = Number($(el).find('.film-poster .tick .tick-dub').text()) || null; + const epsText = $(el).find('.film-poster .tick .tick-eps').length + ? $(el).find('.film-poster .tick .tick-eps').text() + : $(el).find('.film-poster .tick .tick-sub').text(); + + innerObj.episodes.eps = Number(epsText) || null; + + obj.recommended.push(innerObj); + }); + + return obj; +}; diff --git a/src/extractor/extractEpisodes.ts b/src/extractor/extractEpisodes.ts new file mode 100644 index 0000000..0a11216 --- /dev/null +++ b/src/extractor/extractEpisodes.ts @@ -0,0 +1,32 @@ +import { load } from 'cheerio'; + +export interface Episode { + title: string | null; + alternativeTitle: string | null; + id: string | null; + isFiller: boolean; + episodeNumber: number; +} + +export const extractEpisodes = (html: string): Episode[] => { + const $ = load(html); + + const response: Episode[] = []; + $('.ssl-item.ep-item').each((i, el) => { + const obj: Episode = { + title: null, + alternativeTitle: null, + id: null, + isFiller: false, + episodeNumber: i + 1, + }; + obj.title = $(el).attr('title') || null; + obj.id = $(el).attr('href')?.replace('/watch/', '').replace('?', '::') || null; + obj.isFiller = $(el).hasClass('ssl-item-filler'); + + obj.alternativeTitle = $(el).find('.ep-name.e-dynamic-name').attr('data-jname') || null; + + response.push(obj); + }); + return response; +}; diff --git a/src/extractor/extractHomepage.ts b/src/extractor/extractHomepage.ts new file mode 100644 index 0000000..d5b46ec --- /dev/null +++ b/src/extractor/extractHomepage.ts @@ -0,0 +1,221 @@ +import { load } from 'cheerio'; +import { Element } from 'domhandler'; +import { HomePage, SpotlightAnime, TrendingAnime, AnimeFeatured } from '../types/anime'; + +export const extractHomepage = (html: string): HomePage => { + const $ = load(html); + + const response: HomePage = { + spotlight: [], + trending: [], + topAiring: [], + mostPopular: [], + mostFavorite: [], + latestCompleted: [], + latestEpisode: [], + newAdded: [], + topUpcoming: [], + top10: { + today: null, + week: null, + month: null, + }, + genres: [], + }; + + const $spotlight = $('.deslide-wrap .swiper-wrapper .swiper-slide'); + const $trending = $('#trending-home .swiper-container .swiper-slide'); + const $featured = $('#anime-featured .anif-blocks .row .anif-block'); + const $home = $('.block_area.block_area_home'); + const $top10 = $('.block_area .cbox'); + const $genres = $('.sb-genre-list'); + + $($spotlight).each((i: number, el: Element) => { + const obj: SpotlightAnime = { + title: null, + alternativeTitle: null, + id: null, + poster: null, + rank: i + 1, + type: null, + quality: null, + duration: null, + aired: null, + synopsis: null, + episodes: { + sub: null, + dub: null, + eps: null, + }, + }; + obj.id = $(el).find('.desi-buttons a').first().attr('href')?.split('/').at(-1) || null; + obj.poster = $(el).find('.deslide-cover .film-poster-img').attr('data-src') || null; + + const titles = $(el).find('.desi-head-title'); + obj.title = titles.text(); + obj.alternativeTitle = titles.attr('data-jname') || null; + + obj.synopsis = $(el).find('.desi-description').text().trim(); + + const details = $(el).find('.sc-detail'); + obj.type = details.find('.scd-item').eq(0).text().trim(); + obj.duration = details.find('.scd-item').eq(1).text().trim(); + obj.aired = details.find('.scd-item.m-hide').text().trim(); + obj.quality = details.find('.scd-item .quality').text().trim(); + + obj.episodes.sub = Number(details.find('.tick-sub').text().trim()) || null; + obj.episodes.dub = Number(details.find('.tick-dub').text().trim()) || null; + + const epsText = details.find('.tick-eps').length + ? details.find('.tick-eps').text().trim() + : details.find('.tick-sub').text().trim(); + obj.episodes.eps = Number(epsText) || null; + + response.spotlight.push(obj); + }); + $($trending).each((i: number, el: Element) => { + const obj: TrendingAnime = { + title: null, + alternativeTitle: null, + rank: i + 1, + poster: null, + id: null, + }; + + const titleEl = $(el).find('.item .film-title'); + obj.title = titleEl.text(); + obj.alternativeTitle = titleEl.attr('data-jname') || null; + + const imageEl = $(el).find('.film-poster'); + + obj.poster = imageEl.find('img').attr('data-src') || null; + obj.id = imageEl.attr('href')?.split('/').at(-1) || null; + + response.trending.push(obj); + }); + + $($featured).each((i: number, el: Element) => { + const data = $(el) + .find('.anif-block-ul ul li') + .map((index: number, item: Element) => { + const obj: AnimeFeatured = { + title: null, + alternativeTitle: null, + id: null, + poster: null, + type: null, + duration: null, + episodes: { + sub: null, + dub: null, + eps: null, + }, + }; + const titleEl = $(item).find('.film-detail .film-name a'); + obj.title = titleEl.attr('title') || null; + obj.alternativeTitle = titleEl.attr('data-jname') || null; + obj.id = titleEl.attr('href')?.split('/').at(-1) || null; + + obj.poster = $(item).find('.film-poster .film-poster-img').attr('data-src') || null; + + // Extract type (first fdi-item) and duration (second fdi-item if exists) + const infoItems = $(item).find('.fd-infor .fdi-item'); + obj.type = infoItems.eq(0).text().trim() || null; + obj.duration = infoItems.eq(1).text().trim() || null; + + obj.episodes.sub = Number($(item).find('.tick .tick-sub').text()) || null; + obj.episodes.dub = Number($(item).find('.tick .tick-dub').text()) || null; + + const epsText = $(item).find('.fd-infor .tick-eps').length + ? $(item).find('.fd-infor .tick-eps').text() + : $(item).find('.fd-infor .tick-sub').text(); + + obj.episodes.eps = Number(epsText) || null; + + return obj; + }) + .get(); + + const dataType = $(el).find('.anif-block-header').text().replace(/\s+/g, ''); + const normalizedDataType = (dataType.charAt(0).toLowerCase() + + dataType.slice(1)) as keyof HomePage; + + (response[normalizedDataType] as AnimeFeatured[]) = data as AnimeFeatured[]; + }); + + $($home).each((i: number, el: Element) => { + const data = $(el) + .find('.tab-content .film_list-wrap .flw-item') + .map((index: number, item: Element) => { + const obj: AnimeFeatured = { + title: null, + alternativeTitle: null, + id: null, + poster: null, + type: null, // Default + episodes: { + sub: null, + dub: null, + eps: null, + }, + }; + const titleEl = $(item).find('.film-detail .film-name .dynamic-name'); + obj.title = titleEl.attr('title') || null; + obj.alternativeTitle = titleEl.attr('data-jname') || null; + obj.id = titleEl.attr('href')?.split('/').at(-1) || null; + + obj.poster = $(item).find('.film-poster img').attr('data-src') || null; + + const episodesEl = $(item).find('.film-poster .tick'); + obj.episodes.sub = Number($(episodesEl).find('.tick-sub').text()) || null; + obj.episodes.dub = Number($(episodesEl).find('.tick-dub').text()) || null; + + const epsText = $(episodesEl).find('.tick-eps').length + ? $(episodesEl).find('.tick-eps').text() + : $(episodesEl).find('.tick-sub').text(); + + obj.episodes.eps = Number(epsText) || null; + + return obj; + }) + .get(); + + const dataType = $(el).find('.cat-heading').text().replace(/\s+/g, ''); + const normalizedDataType = (dataType.charAt(0).toLowerCase() + + dataType.slice(1)) as keyof HomePage; + + if ((normalizedDataType as string) === 'newOnHiAnime') { + response.newAdded = data; + } else if (normalizedDataType in response) { + (response[normalizedDataType] as AnimeFeatured[]) = data as AnimeFeatured[]; + } + }); + + const extractTopTen = (id: string): TrendingAnime[] => { + const res = $top10 + .find(`${id} ul li`) + .map((i: number, el: Element) => { + const obj: TrendingAnime = { + title: $(el).find('.film-name a').text() || null, + rank: i + 1, + alternativeTitle: $(el).find('.film-name a').attr('data-jname') || null, + id: $(el).find('.film-name a').attr('href')?.split('/').pop() || null, + poster: $(el).find('.film-poster img').attr('data-src') || null, + }; + return obj; + }) + .get(); + return res; + }; + + response.top10.today = extractTopTen('#top-viewed-day'); + response.top10.week = extractTopTen('#top-viewed-week'); + response.top10.month = extractTopTen('#top-viewed-month'); + $($genres) + .find('li') + .each((i: number, el: Element) => { + const genre = $(el).find('a').attr('title')?.toLocaleLowerCase() || ''; + response.genres.push(genre); + }); + return response; +}; diff --git a/src/extractor/extractListpage.ts b/src/extractor/extractListpage.ts new file mode 100644 index 0000000..964e0bf --- /dev/null +++ b/src/extractor/extractListpage.ts @@ -0,0 +1,141 @@ +import { load } from 'cheerio'; +import { Element } from 'domhandler'; +import { AnimeFeatured, TrendingAnime } from '../types/anime'; + +export interface ListPageResponse { + pageInfo: { + currentPage: number; + hasNextPage: boolean; + totalPages: number; + }; + response: ListPageAnime[]; + top10: { + today: TrendingAnime[] | null; + week: TrendingAnime[] | null; + month: TrendingAnime[] | null; + }; + genres: string[]; +} + +export interface ListPageAnime extends AnimeFeatured { + duration: string | null; +} + +export const extractListPage = (html: string): ListPageResponse => { + const $ = load(html); + + const response: ListPageAnime[] = []; + const items = $('.flw-item'); + if (items.length < 1) { + return { + pageInfo: { + currentPage: 1, + hasNextPage: false, + totalPages: 1, + }, + response: [], + top10: { + today: [], + week: [], + month: [], + }, + genres: [], + }; + } + $('.block_area-content.block_area-list.film_list .film_list-wrap .flw-item').each( + (i: number, el: Element) => { + const obj: ListPageAnime = { + title: null, + alternativeTitle: null, + id: null, + poster: null, + episodes: { + sub: null, + dub: null, + eps: null, + }, + type: null, + duration: null, + }; + + obj.poster = $(el).find('.film-poster .film-poster-img').attr('data-src') || null; + obj.episodes.sub = Number($(el).find('.film-poster .tick .tick-sub').text()) || null; + obj.episodes.dub = Number($(el).find('.film-poster .tick .tick-dub').text()) || null; + + const epsText = $(el).find('.film-poster .tick .tick-eps').length + ? $(el).find('.film-poster .tick .tick-eps').text() + : $(el).find('.film-poster .tick .tick-sub').text(); + obj.episodes.eps = Number(epsText) || null; + + const titleEl = $(el).find('.film-detail .film-name .dynamic-name'); + + obj.title = titleEl.text(); + obj.alternativeTitle = titleEl.attr('data-jname') || null; + const href = titleEl.attr('href') || ''; + const id = href.split('/').at(-1) || ''; + obj.id = id.includes('?ref=') ? id.split('?')[0] : id; + + obj.type = $(el).find('.fd-infor .fdi-item').first().text(); + obj.duration = $(el).find('.fd-infor .fdi-duration').text(); + + response.push(obj); + } + ); + + const paginationEl = $('.pre-pagination .pagination .page-item'); + + let currentPage: number, hasNextPage: boolean, totalPages: number; + if (!paginationEl.length) { + currentPage = 1; + hasNextPage = false; + totalPages = 1; + } else { + currentPage = Number(paginationEl.find('.active .page-link').text()); + hasNextPage = !paginationEl.last().hasClass('active'); + totalPages = hasNextPage + ? Number(paginationEl.last().find('.page-link').attr('href')?.split('page=').at(-1)) || 1 + : Number(paginationEl.last().find('.page-link').text()) || 1; + } + + const pageInfo = { + totalPages, + currentPage, + hasNextPage, + }; + + const $top10 = $('.block_area .cbox'); + const $genres = $('.sb-genre-list'); + + const extractTopTen = (id: string): TrendingAnime[] => { + const res = $top10 + .find(`${id} ul li`) + .map((i: number, el: Element) => { + const obj: TrendingAnime = { + title: $(el).find('.film-name a').text() || null, + rank: i + 1, + alternativeTitle: $(el).find('.film-name a').attr('data-jname') || null, + id: $(el).find('.film-name a').attr('href')?.split('/').pop() || null, + poster: $(el).find('.film-poster img').attr('data-src') || null, + }; + return obj; + }) + .get(); + return res; + }; + + const top10 = { + today: extractTopTen('#top-viewed-day'), + week: extractTopTen('#top-viewed-week'), + month: extractTopTen('#top-viewed-month'), + }; + + const genres: string[] = []; + $($genres) + .find('li') + .each((i: number, el: Element) => { + const genre = $(el).find('a').attr('title')?.toLocaleLowerCase() || ''; + if (genre) genres.push(genre); + }); + + return { pageInfo, response, top10, genres }; +}; diff --git a/src/extractor/extractNews.ts b/src/extractor/extractNews.ts new file mode 100644 index 0000000..3a1166a --- /dev/null +++ b/src/extractor/extractNews.ts @@ -0,0 +1,49 @@ +import * as cheerio from 'cheerio'; + +export interface News { + id: string | null; + title: string | null; + description: string | null; + thumbnail: string | null; + uploadedAt: string | null; + url: string | null; +} + +export interface NewsResponse { + news: News[]; + total: number; +} + +export const extractNews = (html: string): NewsResponse => { + const $ = cheerio.load(html); + const news: News[] = []; + + $('.zr-news-list .item').each((i, el) => { + const obj: News = { + id: null, + title: null, + description: null, + thumbnail: null, + uploadedAt: null, + url: null, + }; + + const link = $(el).find('.zrn-title').attr('href'); + obj.id = link?.split('/').pop() || null; + obj.url = link || null; + + obj.title = $(el).find('.news-title').text().trim() || null; + obj.description = $(el).find('.description').text().trim() || null; + obj.thumbnail = $(el).find('.zrn-image').attr('src') || null; + obj.uploadedAt = $(el).find('.time-posted').text().trim() || null; + + if (obj.title) { + news.push(obj); + } + }); + + return { + news, + total: news.length, + }; +}; diff --git a/src/extractor/extractNextEpisodeSchedule.ts b/src/extractor/extractNextEpisodeSchedule.ts new file mode 100644 index 0000000..f76558d --- /dev/null +++ b/src/extractor/extractNextEpisodeSchedule.ts @@ -0,0 +1,18 @@ +import { load } from 'cheerio'; + +export const extractNextEpisodeSchedule = (html: string): string | null => { + const $ = load(html); + + // Try to get schedule from data-value attribute + const scheduleElement = $('#schedule-date'); + const scheduleDate = scheduleElement.attr('data-value'); + + if (scheduleDate) { + return scheduleDate; + } + + // Fallback: try to get from text content + const rawString = $('.tick-item.tick-eps.schedule').text().trim(); + + return rawString || null; +}; diff --git a/src/extractor/extractSchedule.ts b/src/extractor/extractSchedule.ts new file mode 100644 index 0000000..af8e511 --- /dev/null +++ b/src/extractor/extractSchedule.ts @@ -0,0 +1,34 @@ +import { load } from 'cheerio'; + +export interface ScheduledAnime { + title: string | null; + alternativeTitle: string | null; + id: string | null; + time: string | null; + episode: number | null; +} + +export const extractSchedule = (html: string): ScheduledAnime[] => { + const $ = load(html); + + const response: ScheduledAnime[] = []; + $('a').each((i, element) => { + const obj: ScheduledAnime = { + title: null, + alternativeTitle: null, + id: null, + time: null, + episode: null, + }; + + const el = $(element); + obj.id = el.attr('href')?.replace('/', '') || null; + obj.time = el.find('.time').text() || null; + obj.title = el.find('.film-name').text().trim() || null; + obj.alternativeTitle = el.find('.film-name').attr('data-jname')?.trim() || null; + obj.episode = Number(el.find('.btn-play').text().trim().split('Episode ').pop()) || null; + + response.push(obj); + }); + return response; +}; diff --git a/src/extractor/extractSuggestions.ts b/src/extractor/extractSuggestions.ts new file mode 100644 index 0000000..8dc4762 --- /dev/null +++ b/src/extractor/extractSuggestions.ts @@ -0,0 +1,50 @@ +import { load } from 'cheerio'; +import { Element } from 'domhandler'; + +export interface Suggestion { + title: string | null; + alternativeTitle: string | null; + poster: string | null; + id: string | null; + aired: string | null; + type: string | null; + duration: string | null; +} + +export const extractSuggestions = (html: string): Suggestion[] => { + const $ = load(html); + + const response: Suggestion[] = []; + const allEl = $('.nav-item'); + const items = allEl.toArray().splice(0, allEl.length - 2); + $(items).each((i: number, el: Element) => { + const obj: Suggestion = { + title: null, + alternativeTitle: null, + poster: null, + id: null, + aired: null, + type: null, + duration: null, + }; + obj.id = $(el).attr('href')?.split('/').pop()?.split('?').at(0) || null; + obj.poster = $(el).find('.film-poster-img').attr('data-src') || null; + const titleEL = $(el).find('.film-name'); + obj.title = titleEL.text() || null; + obj.alternativeTitle = titleEL.attr('data-jname') || null; + const infoEl = $(el).find('.film-infor'); + obj.aired = infoEl.find('span').first().text() || null; + obj.type = infoEl + .contents() + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .filter((i: number, el: any) => { + return el.type === 'text' && $(el).text().trim() !== ''; + }) + .text() + .trim(); + obj.duration = infoEl.find('span').last().text() || null; + + response.push(obj); + }); + return response; +}; diff --git a/src/extractor/extractTopSearch.ts b/src/extractor/extractTopSearch.ts new file mode 100644 index 0000000..c50c076 --- /dev/null +++ b/src/extractor/extractTopSearch.ts @@ -0,0 +1,25 @@ +import * as cheerio from 'cheerio'; + +export interface TopSearchAnime { + title: string | null; + link: string | null; + id: string | null; +} + +export const extractTopSearch = (html: string): TopSearchAnime[] => { + const $ = cheerio.load(html); + const topSearch: TopSearchAnime[] = []; + + $('.xhashtag .item').each((i, el) => { + const link = $(el).attr('href') || null; + const id = link ? link.split('/').pop()?.split('?')[0] || null : null; + + topSearch.push({ + title: $(el).text().trim() || null, + link: link, + id: id, + }); + }); + + return topSearch; +}; diff --git a/src/middleware/protect.ts b/src/middleware/protect.ts new file mode 100644 index 0000000..9c70552 --- /dev/null +++ b/src/middleware/protect.ts @@ -0,0 +1,16 @@ +import { Context, Next } from 'hono'; +import { NotFoundError } from '../utils/errors'; + +const protect = async (c: Context, next: Next) => { + try { + const ip = c.req.header('X-Forwarded-For') ?? null; + + if (!ip) throw new NotFoundError('404 Page Not Found'); + + await next(); + } catch { + throw new NotFoundError('404 Page Not Found'); + } +}; + +export default protect; diff --git a/src/routes/routes.ts b/src/routes/routes.ts new file mode 100644 index 0000000..c852a4d --- /dev/null +++ b/src/routes/routes.ts @@ -0,0 +1,43 @@ +import { Hono } from 'hono'; +import handler from '../utils/handler'; + +import homepageController from '../controllers/homepage.controller'; +import detailpageController from '../controllers/detailpage.controller'; +import listpageController from '../controllers/listpage.controller'; +import searchController from '../controllers/search.controller'; +import suggestionController from '../controllers/suggestion.controller'; +import charactersController from '../controllers/characters.controller'; +import characterDetailConroller from '../controllers/characterDetail.controller'; +import episodesController from '../controllers/episodes.controller'; +import allGenresController from '../controllers/allGenres.controller'; +import nextEpisodeScheduleController from '../controllers/nextEpisodeSchedule.controller'; +import filterController from '../controllers/filter.controller'; +import filterOptions from '../utils/filter'; +import newsController from '../controllers/news.controller'; +import randomController from '../controllers/random.controller'; +import schedulesController from '../controllers/schedules.controller'; +import topSearchController from '../controllers/topSearch.controller'; + +const router = new Hono(); + +router.get('/home', handler(homepageController)); +router.get('/top-search', handler(topSearchController)); +router.get('/schedules', handler(schedulesController)); +router.get('/schedule/next/:id', handler(nextEpisodeScheduleController)); +router.get('/anime/:id', handler(detailpageController)); +router.get('/animes/:query/:category?', handler(listpageController)); +router.get('/search', handler(searchController)); +router.get( + '/filter/options', + handler(async () => filterOptions) +); +router.get('/filter', handler(filterController)); +router.get('/suggestion', handler(suggestionController)); +router.get('/characters/:id', handler(charactersController)); +router.get('/character/:id', handler(characterDetailConroller)); +router.get('/episodes/:id', handler(episodesController)); +router.get('/genres', handler(allGenresController)); +router.get('/news', handler(newsController)); +router.get('/random', handler(randomController)); + +export default router; diff --git a/src/services/axiosInstance.ts b/src/services/axiosInstance.ts new file mode 100644 index 0000000..a0ca478 --- /dev/null +++ b/src/services/axiosInstance.ts @@ -0,0 +1,103 @@ +import config from '../config/config'; + +const MAX_RETRIES = 3; +const RETRY_DELAY = 1000; +const TIMEOUT = 10000; + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +export const axiosInstance = async ( + endpoint: string, + options: { headers?: Record; retries?: number } = {} +) => { + const { headers: customHeaders = {}, retries = MAX_RETRIES } = options; + const url = config.baseurl + endpoint; + let lastError = null; + + for (let attempt = 0; attempt < retries; attempt++) { + try { + if (attempt > 0) { + const delay = RETRY_DELAY * Math.pow(2, attempt - 1); + console.log(`Retry attempt ${attempt + 1}/${retries} after ${delay}ms delay...`); + await sleep(delay); + } + + console.log(`Fetching (attempt ${attempt + 1}/${retries}): ${url}`); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), TIMEOUT); + + const response = await fetch(url, { + headers: { + ...(config.headers || {}), + ...customHeaders, + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate, br', + Connection: 'keep-alive', + 'Upgrade-Insecure-Requests': '1', + 'Cache-Control': 'max-age=0', + }, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + console.log(`Response status: ${response.status}`); + + if (response.status === 429) { + const retryAfter = response.headers.get('retry-after'); + const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : RETRY_DELAY * 2; + console.warn(`Rate limited. Waiting ${waitTime}ms before retry...`); + await sleep(waitTime); + continue; + } + + if (response.status >= 500 && response.status < 600) { + throw new Error(`Server error: HTTP ${response.status}`); + } + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const data = await response.text(); + + if (!data || data.length === 0) { + throw new Error('Empty response received'); + } + + console.log(`Success: Received data length: ${data.length}`); + + return { + success: true, + data, + }; + } catch (error: unknown) { + if (error instanceof Error) { + lastError = error; + console.error( + `Fetch error (attempt ${attempt + 1}/${retries}) for ${endpoint}:`, + error.message + ); + + if (error.name === 'AbortError') { + lastError = new Error('Request timeout - the external API took too long to respond'); + } + + if (error.message.includes('HTTP 40') && !error.message.includes('429')) { + break; + } + } + + if (attempt === retries - 1) { + break; + } + } + } + + return { + success: false, + message: lastError?.message || 'Unknown error occurred', + }; +}; diff --git a/src/types/anime.ts b/src/types/anime.ts new file mode 100644 index 0000000..4a17889 --- /dev/null +++ b/src/types/anime.ts @@ -0,0 +1,79 @@ +export interface AnimeEpisodes { + sub: number | null; + dub: number | null; + eps: number | null; +} + +export interface AnimeCommon { + title: string | null; + alternativeTitle: string | null; + id: string | null; + poster: string | null; +} + +export interface AnimeFeatured extends AnimeCommon { + type: string | null; + duration?: string | null; + episodes: AnimeEpisodes; +} + +export interface SpotlightAnime extends AnimeFeatured { + rank: number; + quality: string | null; + duration: string | null; + aired: string | null; + synopsis: string | null; +} + +export interface TrendingAnime extends AnimeCommon { + rank: number; +} + +export interface HomePage { + spotlight: SpotlightAnime[]; + trending: TrendingAnime[]; + topAiring: AnimeFeatured[]; + mostPopular: AnimeFeatured[]; + mostFavorite: AnimeFeatured[]; + latestCompleted: AnimeFeatured[]; + latestEpisode: AnimeFeatured[]; + newAdded: AnimeFeatured[]; + topUpcoming: AnimeFeatured[]; + top10: { + today: TrendingAnime[] | null; + week: TrendingAnime[] | null; + month: TrendingAnime[] | null; + }; + genres: string[]; +} + +export interface Season { + title: string | null; + id: string | null; + alternativeTitle: string | null; + poster: string | null; + isActive: boolean; +} + +export interface DetailAnime extends AnimeFeatured { + japanese: string | null; + rating: string | null; + is18Plus: boolean; + synopsis: string | null; + synonyms: string | null; + aired: { + from: string | null; + to: string | null; + }; + premiered: string | null; + duration: string | null; + status: string | null; + MAL_score: string | null; + genres: string[]; + studios: string[]; + producers: string[]; + moreSeasons: Season[]; + related: AnimeFeatured[]; + mostPopular: AnimeFeatured[]; + recommended: (AnimeFeatured & { is18Plus: boolean; duration: string | null })[]; +} diff --git a/src/utils/errors.ts b/src/utils/errors.ts new file mode 100644 index 0000000..f9160d5 --- /dev/null +++ b/src/utils/errors.ts @@ -0,0 +1,21 @@ +export class AppError extends Error { + statusCode: number; + details: unknown; + + constructor(message: string, statusCode: number = 500, details: unknown = null) { + super(message); + this.statusCode = statusCode; + this.details = details; + } +} +export class NotFoundError extends AppError { + constructor(message: string = 'resource not found', details: unknown = null) { + super(message, 404, details); + } +} + +export class validationError extends AppError { + constructor(message: string = 'validaion failed', details: unknown = null) { + super(message, 400, details); + } +} diff --git a/src/utils/filter.ts b/src/utils/filter.ts new file mode 100644 index 0000000..2aad5fe --- /dev/null +++ b/src/utils/filter.ts @@ -0,0 +1,77 @@ +const filterOptions = { + type: ['all', 'movie', 'tv', 'ova', 'special', 'music'], + status: ['all', 'finished_airing', 'currently_airing', 'not_yet_aired'], + rated: ['all', 'g', 'pg', 'pg-13', 'r', 'r+', 'rx'], + score: [ + 'all', + 'appalling', + 'horrible', + 'very_bad', + 'bad', + 'average', + 'fine', + 'good', + 'very_good', + 'great', + 'masterpiece', + ], + season: ['all', 'spring', 'summer', 'fall', 'winter'], + language: ['all', 'sub', 'dub', 'sub_dub'], + sort: [ + 'default', + 'recently_added', + 'recently_updated', + 'score', + 'name_az', + 'release_date', + 'most_watched', + ], + genres: [ + 'action', + 'adventure', + 'cars', + 'comedy', + 'dementia', + 'demons', + 'mystery', + 'drama', + 'ecchi', + 'fantasy', + 'game', + '', + 'historical', + 'horror', + 'kids', + 'magic', + 'martial_arts', + 'mecha', + 'music', + 'parody', + 'samurai', + 'romance', + 'school', + 'sci-fi', + 'shoujo', + 'shoujo_ai', + 'shounen', + 'shounen_ai', + 'space', + 'sports', + 'super_power', + 'vampire', + '', + '', + 'harem', + 'slice_of_life', + 'supernatural', + 'military', + 'police', + 'psychological', + 'thriller', + 'seinen', + 'josei', + 'isekai', + ], +}; + +export default filterOptions; diff --git a/src/utils/handler.ts b/src/utils/handler.ts new file mode 100644 index 0000000..cd19f4d --- /dev/null +++ b/src/utils/handler.ts @@ -0,0 +1,23 @@ +import { Context, Next } from 'hono'; +import { fail, success } from './response'; +import { AppError } from './errors'; + +const handler = (fn: (c: Context, next: Next) => Promise | unknown) => { + return async (c: Context, next: Next) => { + try { + const result = await fn(c, next); + + return success(c, result, 200); + } catch (error: unknown) { + if (error instanceof AppError) { + return fail(c, error.message, error.statusCode, error.details); + } + if (error instanceof Error) { + console.error(error.message); + return fail(c, error.message, 500); + } + return fail(c, 'Internal server error', 500); + } + }; +}; +export default handler; diff --git a/src/utils/logger.ts b/src/utils/logger.ts new file mode 100644 index 0000000..4dcc3f7 --- /dev/null +++ b/src/utils/logger.ts @@ -0,0 +1,64 @@ +import config from '../config/config'; + +const LOG_LEVELS = { + ERROR: 0, + WARN: 1, + INFO: 2, + DEBUG: 3, +}; + +const currentLevel = LOG_LEVELS[config.logLevel as keyof typeof LOG_LEVELS] ?? LOG_LEVELS.INFO; + +const formatMessage = (level: string, message: string, data: unknown) => { + const timestamp = new Date().toISOString(); + const prefix = `[${timestamp}] [${level}]`; + + if (data) { + return `${prefix} ${message} ${JSON.stringify(data, null, 2)}`; + } + + return `${prefix} ${message}`; +}; + +export const logger = { + error: (message: string, data: unknown = null) => { + if (currentLevel >= LOG_LEVELS.ERROR) { + console.error(formatMessage('ERROR', message, data)); + } + }, + + warn: (message: string, data: unknown = null) => { + if (currentLevel >= LOG_LEVELS.WARN) { + console.warn(formatMessage('WARN', message, data)); + } + }, + + info: (message: string, data: unknown = null) => { + if (currentLevel >= LOG_LEVELS.INFO) { + console.log(formatMessage('INFO', message, data)); + } + }, + + debug: (message: string, data: unknown = null) => { + if (currentLevel >= LOG_LEVELS.DEBUG) { + console.log(formatMessage('DEBUG', message, data)); + } + }, + + request: (method: string, url: string, params: unknown = null) => { + if (currentLevel >= LOG_LEVELS.INFO) { + console.log(formatMessage('INFO', `${method} ${url}`, params)); + } + }, + + response: (status: number, url: string, duration: number | null = null) => { + if (currentLevel >= LOG_LEVELS.INFO) { + const msg = duration + ? `Response ${status} for ${url} (${duration}ms)` + : `Response ${status} for ${url}`; + console.log(formatMessage('INFO', msg, null)); + } + }, +}; + +export default logger; diff --git a/src/utils/response.ts b/src/utils/response.ts new file mode 100644 index 0000000..a26c759 --- /dev/null +++ b/src/utils/response.ts @@ -0,0 +1,15 @@ +import { Context } from 'hono'; +import { ContentfulStatusCode } from 'hono/utils/http-status'; + +export const success = (c: Context, data: unknown, statusCode: number = 200) => { + return c.json({ success: true, data }, statusCode as ContentfulStatusCode); +}; + +export const fail = ( + c: Context, + message: string = 'internal server error', + statusCode: number = 500, + details: unknown = null +) => { + return c.json({ success: false, message, details }, statusCode as ContentfulStatusCode); +}; diff --git a/src/utils/saveHtml.ts b/src/utils/saveHtml.ts new file mode 100644 index 0000000..5521e0c --- /dev/null +++ b/src/utils/saveHtml.ts @@ -0,0 +1,19 @@ +import * as path from 'path'; + +const saveHtml = async (html: string, fileName: string) => { + console.log(html); + + try { + const fullPath = path.join(import.meta.dir + '../../../htmls/' + fileName); + + console.log(fullPath); + + await Bun.write(fullPath, html); + } catch (error: unknown) { + if (error instanceof Error) { + console.log('something went wrong' + error.message); + } + } +}; + +export default saveHtml; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bce0ebd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": [ + "ESNext", + "DOM", + "DOM.Iterable" + ], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowJs": true, + "types": [ + "bun-types" + ], + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "noEmit": false, + "outDir": "./src/assets/js", + "esModuleInterop": true + } +} \ No newline at end of file diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..e239327 --- /dev/null +++ b/vercel.json @@ -0,0 +1,9 @@ +{ + "version": 2, + "rewrites": [ + { + "source": "/(.*)", + "destination": "/api" + } + ] +} \ No newline at end of file diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..10f27e1 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['scripts/tests/vitest/**/*.test.ts'], + reporters: ['verbose'], + }, +});