diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..7bbdfe0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,19 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + - package-ecosystem: "npm" + directory: "/src" + schedule: + interval: "weekly" + - package-ecosystem: "terraform" + directory: "/terraform" + schedule: + interval: "weekly" diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 87cec4e..b91cb25 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -3,10 +3,7 @@ name: Tech Report API Pipeline on: [push] env: - PIPELINE_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} PIPELINE_SA_KEY: ${{ secrets.GCP_SA_KEY }} - PIPELINE_PROJECT_DATABASE_DEV: ${{ secrets.GCP_PROJECT_DATABASE_DEV }} - PIPELINE_PROJECT_DATABASE_PROD: ${{ secrets.GCP_PROJECT_DATABASE_PROD }} PIPELINE_GOOGLE_SERVICE_ACCOUNT_CLOUD_FUNCTIONS: ${{ secrets.GCP_SERVICE_ACCOUNT_CLOUD_FUNCTIONS }} PIPELINE_GOOGLE_SERVICE_ACCOUNT_API_GATEWAY: ${{ secrets.GCP_SERVICE_ACCOUNT_API_GATEWAY }} @@ -15,18 +12,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 - with: - python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install pytest - if [ -f requirements.txt ]; then python -m pip install -r requirements.txt; fi - - name: Test with pytest - run: | - python -m pytest -W "ignore" + - run: | + cd src + npm ci + npm run test deploy_development: if: github.ref == 'refs/heads/development' @@ -40,30 +29,29 @@ jobs: - name: Google Cloud Auth uses: 'google-github-actions/auth@v2' with: - project_id: ${{ env.PIPELINE_PROJECT_ID }} + project_id: 'httparchive' credentials_json: ${{ env.PIPELINE_SA_KEY }} - uses: hashicorp/setup-terraform@v3 - + - name: Terraform fmt id: fmt run: terraform fmt -check continue-on-error: true - + - name: Terraform Init id: init run: terraform init - + - name: Terraform Validate id: validate run: terraform validate -no-color - + - name: Terraform Plan id: plan run: | terraform plan -no-color -var="google_service_account_cloud_functions=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_CLOUD_FUNCTIONS }}" \ - -var="google_service_account_api_gateway=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_API_GATEWAY }}" \ - -var="project_database=${{ env.PIPELINE_PROJECT_DATABASE_DEV }}" + -var="google_service_account_api_gateway=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_API_GATEWAY }}" continue-on-error: true - name: Terraform Plan status @@ -75,9 +63,8 @@ jobs: run: | terraform apply -var="google_service_account_cloud_functions=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_CLOUD_FUNCTIONS }}" \ -var="google_service_account_api_gateway=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_API_GATEWAY }}" \ - -var="project_database=${{ env.PIPELINE_PROJECT_DATABASE_DEV }}" \ -auto-approve - + deploy_production: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest @@ -90,30 +77,29 @@ jobs: - name: Google Cloud Auth uses: 'google-github-actions/auth@v2' with: - project_id: ${{ env.PIPELINE_PROJECT_ID }} + project_id: 'httparchive' credentials_json: ${{ env.PIPELINE_SA_KEY }} - uses: hashicorp/setup-terraform@v3 - + - name: Terraform fmt id: fmt run: terraform fmt -check continue-on-error: true - + - name: Terraform Init id: init run: terraform init - + - name: Terraform Validate id: validate run: terraform validate -no-color - + - name: Terraform Plan id: plan run: | terraform plan -no-color -var="google_service_account_cloud_functions=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_CLOUD_FUNCTIONS }}" \ - -var="google_service_account_api_gateway=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_API_GATEWAY }}" \ - -var="project_database=${{ env.PIPELINE_PROJECT_DATABASE_PROD }}" + -var="google_service_account_api_gateway=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_API_GATEWAY }}" continue-on-error: true - name: Terraform Plan status @@ -125,7 +111,5 @@ jobs: run: | terraform apply -var="google_service_account_cloud_functions=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_CLOUD_FUNCTIONS }}" \ -var="google_service_account_api_gateway=${{ env.PIPELINE_GOOGLE_SERVICE_ACCOUNT_API_GATEWAY }}" \ - -var="project_database=${{ env.PIPELINE_PROJECT_DATABASE_PROD }}" \ -auto-approve - - \ No newline at end of file + diff --git a/.gitignore b/.gitignore index 35f4544..7230e41 100644 --- a/.gitignore +++ b/.gitignore @@ -24,13 +24,6 @@ spec.yaml # CLI .DS_Store -# asdf -/.tool-versions -# python -__pycache__ -.pytest_cache -.venv - -utils.txt -logs +node_modules/ +src/coverage/ diff --git a/README.md b/README.md index c741a3a..ed9718d 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,132 @@ -# tech-report-apis +# Technology Reports API (Node.js) -APIs for the HTTP Archive Technology Report +This is a unified Google Cloud Run function that provides technology metrics and information via various endpoints. -## API +## Setup -## Endpoints +### Prerequisites -### `GET /adoption` +- Node.js 18+ +- npm +- Google Cloud account with necessary permissions +- Set environment variables: + + ```bash + export PROJECT=httparchive + export DATABASE=tech-report-api-prod + ``` + +### Local Development + + ```bash + npm install + npm start:functions + ``` + +The API will be available at + +### Google Cloud Functions Mode + + ```bash + npm install + npm run start:functions + ``` + +The function will run on `http://localhost:8080` + +## Deployment + +### Deploy to Google Cloud Run Function + +```bash +# Deploy to Google Cloud Functions +gcloud functions deploy tech-report-api \ + --runtime nodejs22 \ + --trigger-http \ + --allow-unauthenticated \ + --entry-point api \ + --source . +``` + +## API Endpoints + +## Features + +- **ETag Support**: All endpoints include ETag headers for efficient caching +- **CORS Enabled**: Cross-origin requests are supported +- **Cache Headers**: 6-hour cache control for static data +- **Health Check**: GET `/` returns health status +- **RESTful API**: All endpoints follow REST conventions + +### `GET /` + +Health check + +### `GET /technologies` + +Lists available technologies with optional filtering. #### Parameters -The following parameters can be used to filter the data: +- `technology` (optional): Filter by technology name(s) - comma-separated list +- `category` (optional): Filter by category - comma-separated list +- `onlyname` (optional): If present, returns only technology names -- `geo` (`required`): A string representing the geographic location. -- `technology` (`required`): A comma-separated string representing the technology name(s). -- `rank` (`required`): An string representing the rank. -- `start` (optional): A string representing the start date in the format `YYYY-MM-DD`. -- `end` (optional): A string representing the end date in the format `YYYY-MM-DD`. +#### Example Request & Response -#### Response +```bash +curl --request GET \ + --url 'https://{{HOST}}/v1/technologies?category=Live%20chat%2C%20blog&technology=Smartsupp' +``` + +Returns a JSON object with the following schema: + +```json +[ + { + "technology": "Smartsupp", + "category": "Live chat", + "description": "Smartsupp is a live chat tool that offers visitor recording feature.", + "icon": "Smartsupp.svg", + "origins": { + "mobile": 24115, + "desktop": 20250 + } + } +] +``` ```bash curl --request GET \ - --url 'https://{{HOST}}/v1/adoption?start=2023-01-01&end=2023-09-01&geo=Mexico&technology=GoCache&rank=ALL' + --url 'https://{{HOST}}/v1/technologies?onlyname' ``` Returns a JSON object with the following schema: ```json [ - { - "technology": "GoCache", - "geo": "Mexico", - "date": "2023-06-01", - "rank": "ALL", - "adoption": { - "mobile": 19, - "desktop": 11 - } - }, + "1C-Bitrix", + "2B Advice", + "33Across", + "34SP.com", + "4-Tell", + "42stores", + "51.LA", + "5centsCDN", ... -] +} ``` ### `GET /categories` -This endpoint can return a full list of categories names or a categories with all the associated technologies +Lists available categories. -#### Parameters +#### Categories Parameters -The following parameters can be used to filter the data: +- `category` (optional): Filter by category name(s) - comma-separated list +- `onlyname` (optional): If present, returns only category names -- `category` (optional): A comma-separated string representing the category name(s). -- `onlyname` (optional): No value required. If present, only the category names will be returned. - -#### Response +#### Categories Response ```bash curl --request GET \ @@ -105,22 +177,58 @@ curl --request GET \ "Analytics", ... ] +``` + +### `GET /adoption` + +Provides technology adoption data. +#### Adoption Parameters + +- `technology` (required): Filter by technology name(s) - comma-separated list +- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest') +- `end` (optional): Filter by date range end (YYYY-MM-DD) +- `geo` (optional): Filter by geographic location +- `rank` (optional): Filter by rank + +#### Adoption Response + +```bash +curl --request GET \ + --url 'https://{{HOST}}/v1/adoption?start=2023-01-01&end=2023-09-01&geo=Mexico&technology=GoCache&rank=ALL' ``` -### `GET /cwv` +Returns a JSON object with the following schema: -#### Parameters +```json +[ + { + "technology": "GoCache", + "geo": "Mexico", + "date": "2023-06-01", + "rank": "ALL", + "adoption": { + "mobile": 19, + "desktop": 11 + } + }, + ... +] +``` -The following parameters can be used to filter the data: +### `GET /cwv` (Core Web Vitals) -- `geo` (`required`): A string representing the geographic location. -- `technology` (`required`): A string representing the technology name. -- `rank` (`required`): An string representing the rank. -- `start` (optional): A string representing the start date in the format `YYYY-MM-DD`. -- `end` (optional): A string representing the end date in the format `YYYY-MM-DD`. +Provides Core Web Vitals metrics for technologies. -#### Response +#### CWV Parameters + +- `technology` (required): Filter by technology name(s) - comma-separated list +- `geo` (required): Filter by geographic location +- `rank` (required): Filter by rank +- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest') +- `end` (optional): Filter by date range end (YYYY-MM-DD) + +#### CWV Response ```bash curl --request GET \ @@ -151,22 +259,21 @@ curl --request GET \ ] } ] - ``` ### `GET /lighthouse` -#### Parameters +Provides Lighthouse scores for technologies. -The following parameters can be used to filter the data: +#### Lighthouse Parameters -- `technology` (`required`): A comma-separated string representing the technology name(s). -- `geo` (`required`): A string representing the geographic location. -- `rank` (`required`): An string representing the rank. -- `start` (optional): A string representing the start date in the format `YYYY-MM-DD`. -- `end` (optional): A string representing the end date in the format `YYYY-MM-DD`. +- `technology` (required): Filter by technology name(s) - comma-separated list +- `geo` (required): Filter by geographic location +- `rank` (required): Filter by rank +- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest') +- `end` (optional): Filter by date range end (YYYY-MM-DD) -#### Response +#### Lighthouse Response ```bash curl --request GET \ @@ -205,17 +312,17 @@ Returns a JSON object with the following schema: ### `GET /page-weight` -#### Parameters +Provides Page Weight metrics for technologies. -The following parameters can be used to filter the data: +#### Page Weight Parameters -- `geo` (`required`): A string representing the geographic location. -- `technology` (`required`): A comma-separated string representing the technology name(s). -- `rank` (`required`): An string representing the rank. -- `start` (optional): A string representing the start date in the format `YYYY-MM-DD`. -- `end` (optional): A string representing the end date in the format `YYYY-MM-DD`. +- `technology` (required): Filter by technology name(s) - comma-separated list +- `geo` (optional): Filter by geographic location +- `rank` (optional): Filter by rank +- `start` (optional): Filter by date range start (YYYY-MM-DD or 'latest') +- `end` (optional): Filter by date range end (YYYY-MM-DD) -#### Response +#### Page Weight Response ```bash curl --request GET \ @@ -240,57 +347,165 @@ Returns a JSON object with the following schema: ] ``` -### `GET /technologies` +### `GET /ranks` -#### Parameters +Lists all available ranks. -The following parameters can be used to filter the data: +### `GET /geos` -- `technology` (optional): A comma-separated string representing the technology name(s) or `ALL`. -- `category` (optional): A comma-separated string representing the category name(s). -- `onlyname` (optional): No value required. If present, only the technology names will be returned. +Lists all available geographic locations. -#### Response +## Testing ```bash -curl --request GET \ - --url 'https://{{HOST}}/v1/technologies?category=Live%20chat%2C%20blog&technology=Smartsupp' +# Run all tests +npm test + +# Run tests with coverage +npm run test ``` -Returns a JSON object with the following schema: +## Response Format + +All API responses follow this format: ```json [ - { - "technology": "Smartsupp", - "category": "Live chat", - "description": "Smartsupp is a live chat tool that offers visitor recording feature.", - "icon": "Smartsupp.svg", - "origins": { - "mobile": 24115, - "desktop": 20250 - } - } + // Array of data objects ] ``` -```bash -curl --request GET \ - --url 'https://{{HOST}}/v1/technologies?onlyname' +Or in case of an error: + +```json +{ + "success": false, + "errors": [ + {"key": "error message"} + ] +} ``` -Returns a JSON object with the following schema: +## Field Selection API Documentation + +### Overview + +The categories and technologies endpoints now support custom field selection, allowing clients to specify exactly which fields they want in the response. This feature helps reduce payload size and improves API performance by returning only the needed data. + +### Endpoints Supporting Field Selection + +- `GET /v1/technologies` +- `GET /v1/categories` + +### Usage + +#### Basic Syntax + +Add a `fields` parameter to your request with comma-separated field names: + +``` +GET /v1/technologies?fields=technology,category +GET /v1/categories?fields=category,description +``` + +#### Examples + +##### Technologies Endpoint + +**Get only technology names and categories:** + +``` +GET /v1/technologies?fields=technology,category +``` + +Response: ```json -[ - "1C-Bitrix", - "2B Advice", - "33Across", - "34SP.com", - "4-Tell", - "42stores", - "51.LA", - "5centsCDN", - ... +{ + "data": [ + { + "technology": "React", + "category": "JavaScript Frameworks" + }, + { + "technology": "Angular", + "category": "JavaScript Frameworks" + } + ] } ``` + +**Get technology names and descriptions:** + +``` +GET /v1/technologies?fields=technology,description +``` + +**Combine with existing filters:** + +``` +GET /v1/technologies?category=JavaScript%20Frameworks&fields=technology,icon +``` + +##### Categories Endpoint + +**Get only category names:** + +``` +GET /v1/categories?fields=category +``` + +**Get categories with descriptions:** + +``` +GET /v1/categories?fields=category,description +``` + +#### Behavior Notes + +1. **Field Priority**: The `fields` parameter takes precedence over other response formatting options, except for `onlyname` +2. **Invalid Fields**: Non-existent fields are silently ignored +3. **Empty Fields**: If no valid fields are specified, the full object is returned +4. **Backward Compatibility**: When `fields` is not specified, endpoints return their default response format +5. **onlyname Override**: The `onlyname` parameter still takes precedence over `fields` for backward compatibility + +#### Available Fields + +##### Technologies Endpoint + +- `technology` - Technology name +- `category` - Category name +- `description` - Technology description +- `icon` - Icon filename +- `origins` - Array of origin companies/organizations + +##### Categories Endpoint + +- `category` - Category name +- Additional fields depend on your data structure + +#### Error Handling + +The field selection feature handles errors gracefully: + +- Invalid field names are ignored +- Empty field lists return full objects +- Malformed field parameters fallback to default behavior + +#### Performance Benefits + +- **Reduced Payload Size**: Only requested fields are included +- **Faster Parsing**: Clients process smaller JSON objects +- **Bandwidth Savings**: Less data transferred over the network +- **Improved Caching**: More specific responses can be cached more effectively + +#### Migration Guide + +Existing API consumers are not affected by this change. The field selection feature is entirely opt-in through the `fields` parameter. + +To adopt field selection: + +1. Identify which fields your application actually uses +2. Add the `fields` parameter with those field names +3. Update your client code to handle the new response structure +4. Test thoroughly with your specific use cases diff --git a/conftest.py b/conftest.py deleted file mode 100644 index ddd5225..0000000 --- a/conftest.py +++ /dev/null @@ -1,2 +0,0 @@ -import pytest -from functions import * \ No newline at end of file diff --git a/functions/adoption/libs/__init__.py b/functions/adoption/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/functions/adoption/libs/network.py b/functions/adoption/libs/network.py deleted file mode 100644 index 76a731f..0000000 --- a/functions/adoption/libs/network.py +++ /dev/null @@ -1,37 +0,0 @@ - -""" -Network - -Handles formatting responses to match the tuple pattern required by -the flask/GCP wrapper for Cloud Functions. -""" -import json -from .utils import convert_to_hashes - -PREFLIGHT_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Content-Type, Timing-Allow-Origin", - "Access-Control-Max-Age": "3600", - } - -HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - "cache-control": "public, max-age=21600", - "Timing-Allow-Origin": "*" - } - -def respond_cors(): - """ - To be used to return OPTIONS responses to satisfy CORS preflight requests. - """ - return ("", 204, PREFLIGHT_HEADERS) - -def respond(result, headers=HEADERS): - """ - To be used to return responses to satisfy CORS requests. - """ - status = 200 if result.success() else 400 - payload = result.result if result.success() else convert_to_hashes(result.errors) - return (json.dumps(payload), status, headers) \ No newline at end of file diff --git a/functions/adoption/libs/queries.py b/functions/adoption/libs/queries.py deleted file mode 100644 index 026cee9..0000000 --- a/functions/adoption/libs/queries.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -from google.cloud import firestore -from google.cloud.firestore_v1.base_query import FieldFilter -from .result import Result -from .utils import convert_to_array - -DB = firestore.Client(project=os.environ.get('PROJECT'), database=os.environ.get('DATABASE')) -TABLE = 'adoption' - -def get_latest_date(): - """Retrieve the latest date in the collection.""" - query = DB.collection(TABLE).order_by('date', direction=firestore.Query.DESCENDING).limit(1) - docs = query.stream() - for doc in docs: - return doc.to_dict().get('date') - return None - -def list_data(params): - - technology_array = convert_to_array(params['technology']) - data = [] - - if 'start' in params and params['start'] == 'latest': - params['start'] = get_latest_date() - - for technology in technology_array: - query = DB.collection(TABLE) - - if 'start' in params: - query = query.where(filter=FieldFilter('date', '>=', params['start'])) - - if 'end' in params: - query = query.where(filter=FieldFilter('date', '<=', params['end'])) - - if 'geo' in params: - query = query.where(filter=FieldFilter('geo', '==', params['geo'])) - - if 'rank' in params: - query = query.where(filter=FieldFilter('rank', '==', params['rank'])) - - query = query.where(filter=FieldFilter('technology', '==', technology)) - - documents = query.stream() - - for doc in documents: - data.append(doc.to_dict()) - - return Result(result=data) \ No newline at end of file diff --git a/functions/adoption/libs/result.py b/functions/adoption/libs/result.py deleted file mode 100644 index 63034b6..0000000 --- a/functions/adoption/libs/result.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Result(): - def __init__(self, status=None, result=None, errors=[]): - self._status = status - self.result = result - self.errors = errors - - def success(self) -> bool: - return not self.failure() - - def failure(self) -> bool: - return len(self.errors) > 0 - - @property - def status(self): - if self._status != None: - return self._status - - return "ok" if self.success else "error" - \ No newline at end of file diff --git a/functions/adoption/libs/utils.py b/functions/adoption/libs/utils.py deleted file mode 100644 index 997c85e..0000000 --- a/functions/adoption/libs/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -from urllib.parse import unquote - -def convert_to_hashes(arr): - hashes_arr = [] - for inner_arr in arr: - hash_dict = {inner_arr[0]: inner_arr[1]} - hashes_arr.append(hash_dict) - return hashes_arr - -def convert_to_array(data_string): - decoded_data = unquote(data_string) - list = decoded_data.split(',') - return list diff --git a/functions/adoption/libs/validator.py b/functions/adoption/libs/validator.py deleted file mode 100644 index 4c202a4..0000000 --- a/functions/adoption/libs/validator.py +++ /dev/null @@ -1,27 +0,0 @@ -from .result import Result - -class Validator(): - def __init__(self, params): - self.params = params - self.errors = [] - self.normalizer_params = self.normalize(params) - - def validate(self): - result = Result(status="ok", result="()") - - if 'geo' not in self.params: - self.add_error("geo", "missing geo parameter") - - if 'technology' not in self.params: - self.add_error("technology", "missing technology parameter") - - if 'rank' not in self.params: - self.add_error("rank", "missing rank parameter") - - return Result(errors=self.errors, result=self.params) - - def add_error(self, key, error): - self.errors.append([key, error]) - - def normalize(self, params): - return "" diff --git a/functions/adoption/main.py b/functions/adoption/main.py deleted file mode 100644 index 65865dc..0000000 --- a/functions/adoption/main.py +++ /dev/null @@ -1,24 +0,0 @@ -import functions_framework - -from .libs.validator import Validator -from .libs.queries import list_data -from .libs.network import respond_cors, respond - -@functions_framework.http -def dispatcher(request): - - if request.method == "OPTIONS": - return respond_cors() - - args = request.args.to_dict() - - validator = Validator(params=args) - result = validator.validate() - - if result.failure(): - print("error", result.errors) - return respond(result) - - response = list_data(result.result) - - return respond(response) diff --git a/functions/adoption/requirements.txt b/functions/adoption/requirements.txt deleted file mode 100644 index f336d0d..0000000 --- a/functions/adoption/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -functions-framework -google-cloud-firestore -pytest \ No newline at end of file diff --git a/functions/categories/libs/__init__.py b/functions/categories/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/functions/categories/libs/network.py b/functions/categories/libs/network.py deleted file mode 100644 index 76a731f..0000000 --- a/functions/categories/libs/network.py +++ /dev/null @@ -1,37 +0,0 @@ - -""" -Network - -Handles formatting responses to match the tuple pattern required by -the flask/GCP wrapper for Cloud Functions. -""" -import json -from .utils import convert_to_hashes - -PREFLIGHT_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Content-Type, Timing-Allow-Origin", - "Access-Control-Max-Age": "3600", - } - -HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - "cache-control": "public, max-age=21600", - "Timing-Allow-Origin": "*" - } - -def respond_cors(): - """ - To be used to return OPTIONS responses to satisfy CORS preflight requests. - """ - return ("", 204, PREFLIGHT_HEADERS) - -def respond(result, headers=HEADERS): - """ - To be used to return responses to satisfy CORS requests. - """ - status = 200 if result.success() else 400 - payload = result.result if result.success() else convert_to_hashes(result.errors) - return (json.dumps(payload), status, headers) \ No newline at end of file diff --git a/functions/categories/libs/queries.py b/functions/categories/libs/queries.py deleted file mode 100644 index 141b14d..0000000 --- a/functions/categories/libs/queries.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -from google.cloud import firestore -from google.cloud.firestore_v1.base_query import FieldFilter, Or -from .result import Result -from .utils import convert_to_array - -DB = firestore.Client( - project=os.environ.get("PROJECT"), database=os.environ.get("DATABASE") -) - -def list_data(params): - ref = DB.collection("categories") - - query = ref.order_by("category", direction=firestore.Query.ASCENDING) - - if "category" in params: - category_array = convert_to_array(params["category"]) - filter_array = [] - for category in category_array: - filter_array.append(FieldFilter("category", "==", category)) - query = query.where(filter=Or(filters=filter_array)) - - documents = query.stream() - data = [] - - if "onlyname" in params: - for doc in documents: - data.append(doc.get("category")) - - else: - for doc in documents: - data.append(doc.to_dict()) - - return Result(result=data) diff --git a/functions/categories/libs/result.py b/functions/categories/libs/result.py deleted file mode 100644 index 63034b6..0000000 --- a/functions/categories/libs/result.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Result(): - def __init__(self, status=None, result=None, errors=[]): - self._status = status - self.result = result - self.errors = errors - - def success(self) -> bool: - return not self.failure() - - def failure(self) -> bool: - return len(self.errors) > 0 - - @property - def status(self): - if self._status != None: - return self._status - - return "ok" if self.success else "error" - \ No newline at end of file diff --git a/functions/categories/libs/utils.py b/functions/categories/libs/utils.py deleted file mode 100644 index 29692f4..0000000 --- a/functions/categories/libs/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -from urllib.parse import unquote - -def convert_to_hashes(arr): - hashes_arr = [] - for inner_arr in arr: - hash_dict = {inner_arr[0]: inner_arr[1]} - hashes_arr.append(hash_dict) - return hashes_arr - -def convert_to_array(data_string): - decoded_data = unquote(data_string) - list = decoded_data.split(',') - return list \ No newline at end of file diff --git a/functions/categories/libs/validator.py b/functions/categories/libs/validator.py deleted file mode 100644 index ff19ca9..0000000 --- a/functions/categories/libs/validator.py +++ /dev/null @@ -1,22 +0,0 @@ -from .result import Result - -class Validator(): - def __init__(self, params): - self.params = params - self.errors = [] - self.normalizer_params = self.normalize(params) - - def validate(self): - result = Result(status="ok", result="()") - - # if 'onlyname' not in self.params: - # if 'category' not in self.params: - # self.add_error("category", "missing category parameter") - - return Result(errors=self.errors, result=self.params) - - def add_error(self, key, error): - self.errors.append([key, error]) - - def normalize(self, params): - return "" diff --git a/functions/categories/main.py b/functions/categories/main.py deleted file mode 100644 index e14f7ac..0000000 --- a/functions/categories/main.py +++ /dev/null @@ -1,24 +0,0 @@ -import functions_framework - -from .libs.validator import Validator -from .libs.queries import list_data -from .libs.network import respond_cors, respond - -@functions_framework.http -def dispatcher(request): - - if request.method == "OPTIONS": - return respond_cors() - - args = request.args.to_dict() - - validator = Validator(params=args) - result = validator.validate() - - if result.failure(): - print("error", result.errors) - return respond(result) - - response = list_data(result.result) - - return respond(response) \ No newline at end of file diff --git a/functions/categories/requirements.txt b/functions/categories/requirements.txt deleted file mode 100644 index f336d0d..0000000 --- a/functions/categories/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -functions-framework -google-cloud-firestore -pytest \ No newline at end of file diff --git a/functions/cwvtech/libs/__init__.py b/functions/cwvtech/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/functions/cwvtech/libs/network.py b/functions/cwvtech/libs/network.py deleted file mode 100644 index d097aa8..0000000 --- a/functions/cwvtech/libs/network.py +++ /dev/null @@ -1,37 +0,0 @@ - -""" -Network - -Handles formatting responses to match the tuple pattern required by -the flask/GCP wrapper for Cloud Functions. -""" -import json -from .utils import convert_to_hashes - -PREFLIGHT_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Content-Type, Timing-Allow-Origin", - "Access-Control-Max-Age": "3600", - } - -HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - "cache-control": "public, max-age=21600", - "Timing-Allow-Origin": "*" - } - -def respond_cors(): - """ - To be used to return OPTIONS responses to satisfy CORS preflight requests. - """ - return ("", 204, PREFLIGHT_HEADERS) - -def respond(result, headers=HEADERS): - """ - To be used to return responses to satisfy CORS requests. - """ - status = 200 if result.success() else 400 - payload = result.result if result.success() else convert_to_hashes(result.errors) - return (json.dumps(payload), status, headers) diff --git a/functions/cwvtech/libs/queries.py b/functions/cwvtech/libs/queries.py deleted file mode 100644 index 72c8ba6..0000000 --- a/functions/cwvtech/libs/queries.py +++ /dev/null @@ -1,42 +0,0 @@ -import os -from google.cloud import firestore -from google.cloud.firestore_v1.base_query import FieldFilter -from .result import Result -from .utils import convert_to_array - -DB = firestore.Client(project=os.environ.get('PROJECT'), database=os.environ.get('DATABASE')) -TABLE = 'core_web_vitals' - -def get_latest_date(): - """Retrieve the latest date in the collection.""" - query = DB.collection(TABLE).order_by('date', direction=firestore.Query.DESCENDING).limit(1) - docs = query.stream() - for doc in docs: - return doc.to_dict().get('date') - return None - -def list_data(params): - technology_array = convert_to_array(params['technology']) - data = [] - - if 'start' in params and params['start'] == 'latest': - params['start'] = get_latest_date() - - for technology in technology_array: - query = DB.collection(TABLE) - - if 'start' in params: - query = query.where(filter=FieldFilter('date', '>=', params['start'])) - if 'end' in params: - query = query.where(filter=FieldFilter('date', '<=', params['end'])) - - query = query.where(filter=FieldFilter('geo', '==', params['geo'])) - query = query.where(filter=FieldFilter('rank', '==', params['rank'])) - query = query.where(filter=FieldFilter('technology', '==', technology)) - - documents = query.stream() - - for doc in documents: - data.append(doc.to_dict()) - - return Result(result=data) diff --git a/functions/cwvtech/libs/result.py b/functions/cwvtech/libs/result.py deleted file mode 100644 index 63034b6..0000000 --- a/functions/cwvtech/libs/result.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Result(): - def __init__(self, status=None, result=None, errors=[]): - self._status = status - self.result = result - self.errors = errors - - def success(self) -> bool: - return not self.failure() - - def failure(self) -> bool: - return len(self.errors) > 0 - - @property - def status(self): - if self._status != None: - return self._status - - return "ok" if self.success else "error" - \ No newline at end of file diff --git a/functions/cwvtech/libs/utils.py b/functions/cwvtech/libs/utils.py deleted file mode 100644 index 29692f4..0000000 --- a/functions/cwvtech/libs/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -from urllib.parse import unquote - -def convert_to_hashes(arr): - hashes_arr = [] - for inner_arr in arr: - hash_dict = {inner_arr[0]: inner_arr[1]} - hashes_arr.append(hash_dict) - return hashes_arr - -def convert_to_array(data_string): - decoded_data = unquote(data_string) - list = decoded_data.split(',') - return list \ No newline at end of file diff --git a/functions/cwvtech/libs/validator.py b/functions/cwvtech/libs/validator.py deleted file mode 100644 index 1797b49..0000000 --- a/functions/cwvtech/libs/validator.py +++ /dev/null @@ -1,27 +0,0 @@ -from .result import Result - -class Validator(): - def __init__(self, params): - self.params = params - self.errors = [] - self.normalizer_params = self.normalize(params) - - def validate(self): - result = Result(status="ok", result="()") - - if 'geo' not in self.params: - self.add_error("geo", "missing geo parameter") - - if 'technology' not in self.params: - self.add_error("technology", "missing technology parameter") - - if 'rank' not in self.params: - self.add_error("rank", "missing rank parameter") - - return Result(errors=self.errors, result=self.params) - - def add_error(self, key, error): - self.errors.append([key, error]) - - def normalize(self, params): - return "" diff --git a/functions/cwvtech/main.py b/functions/cwvtech/main.py deleted file mode 100644 index b68f15e..0000000 --- a/functions/cwvtech/main.py +++ /dev/null @@ -1,24 +0,0 @@ -import functions_framework - -from .libs.validator import Validator -from .libs.queries import list_data -from .libs.network import respond_cors, respond - -@functions_framework.http -def dispatcher(request): - - if request.method == "OPTIONS": - return respond_cors() - - args = request.args.to_dict() - - validator = Validator(params=args) - result = validator.validate() - - if result.failure(): - print("error", result.errors) - return respond(result) - - response = list_data(result.result) - - return respond(response) \ No newline at end of file diff --git a/functions/cwvtech/requirements.txt b/functions/cwvtech/requirements.txt deleted file mode 100644 index f336d0d..0000000 --- a/functions/cwvtech/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -functions-framework -google-cloud-firestore -pytest \ No newline at end of file diff --git a/functions/geos/libs/__init__.py b/functions/geos/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/functions/geos/libs/network.py b/functions/geos/libs/network.py deleted file mode 100644 index 76a731f..0000000 --- a/functions/geos/libs/network.py +++ /dev/null @@ -1,37 +0,0 @@ - -""" -Network - -Handles formatting responses to match the tuple pattern required by -the flask/GCP wrapper for Cloud Functions. -""" -import json -from .utils import convert_to_hashes - -PREFLIGHT_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Content-Type, Timing-Allow-Origin", - "Access-Control-Max-Age": "3600", - } - -HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - "cache-control": "public, max-age=21600", - "Timing-Allow-Origin": "*" - } - -def respond_cors(): - """ - To be used to return OPTIONS responses to satisfy CORS preflight requests. - """ - return ("", 204, PREFLIGHT_HEADERS) - -def respond(result, headers=HEADERS): - """ - To be used to return responses to satisfy CORS requests. - """ - status = 200 if result.success() else 400 - payload = result.result if result.success() else convert_to_hashes(result.errors) - return (json.dumps(payload), status, headers) \ No newline at end of file diff --git a/functions/geos/libs/result.py b/functions/geos/libs/result.py deleted file mode 100644 index 63034b6..0000000 --- a/functions/geos/libs/result.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Result(): - def __init__(self, status=None, result=None, errors=[]): - self._status = status - self.result = result - self.errors = errors - - def success(self) -> bool: - return not self.failure() - - def failure(self) -> bool: - return len(self.errors) > 0 - - @property - def status(self): - if self._status != None: - return self._status - - return "ok" if self.success else "error" - \ No newline at end of file diff --git a/functions/geos/libs/utils.py b/functions/geos/libs/utils.py deleted file mode 100644 index 14dbde0..0000000 --- a/functions/geos/libs/utils.py +++ /dev/null @@ -1,227 +0,0 @@ -def convert_to_hashes(arr): - hashes_arr = [] - for inner_arr in arr: - hash_dict = {inner_arr[0]: inner_arr[1]} - hashes_arr.append(hash_dict) - return hashes_arr - - -COUNTRIES = [ - {"geo": "ALL", "num_origins": "9731427"}, - {"geo": "United States of America", "num_origins": "1707677"}, - {"geo": "India", "num_origins": "826143"}, - {"geo": "Japan", "num_origins": "690984"}, - {"geo": "Germany", "num_origins": "678201"}, - {"geo": "Brazil", "num_origins": "644760"}, - { - "geo": "United Kingdom of Great Britain and Northern Ireland", - "num_origins": "560753", - }, - {"geo": "Russian Federation", "num_origins": "529803"}, - {"geo": "France", "num_origins": "515925"}, - {"geo": "Italy", "num_origins": "503015"}, - {"geo": "Spain", "num_origins": "459739"}, - {"geo": "Indonesia", "num_origins": "401253"}, - {"geo": "Poland", "num_origins": "350837"}, - {"geo": "Canada", "num_origins": "335548"}, - {"geo": "Mexico", "num_origins": "317337"}, - {"geo": "Turkey", "num_origins": "292310"}, - {"geo": "Netherlands", "num_origins": "291785"}, - {"geo": "Argentina", "num_origins": "252487"}, - {"geo": "Australia", "num_origins": "215909"}, - {"geo": "Korea, Republic of", "num_origins": "209013"}, - {"geo": "Philippines", "num_origins": "204637"}, - {"geo": "Colombia", "num_origins": "198020"}, - {"geo": "Malaysia", "num_origins": "193444"}, - {"geo": "Ukraine", "num_origins": "189866"}, - {"geo": "Viet Nam", "num_origins": "176473"}, - {"geo": "Thailand", "num_origins": "167337"}, - {"geo": "Pakistan", "num_origins": "157400"}, - {"geo": "Belgium", "num_origins": "157266"}, - {"geo": "South Africa", "num_origins": "150004"}, - {"geo": "Czechia", "num_origins": "148638"}, - {"geo": "Romania", "num_origins": "148176"}, - {"geo": "Taiwan, Province of China", "num_origins": "147383"}, - {"geo": "Chile", "num_origins": "144592"}, - {"geo": "Greece", "num_origins": "135996"}, - {"geo": "Austria", "num_origins": "135821"}, - {"geo": "Bangladesh", "num_origins": "134081"}, - {"geo": "Peru", "num_origins": "124954"}, - {"geo": "Iran (Islamic Republic of)", "num_origins": "122949"}, - {"geo": "Singapore", "num_origins": "121397"}, - {"geo": "Egypt", "num_origins": "119105"}, - {"geo": "Hungary", "num_origins": "117857"}, - {"geo": "Nigeria", "num_origins": "115407"}, - {"geo": "Portugal", "num_origins": "113035"}, - {"geo": "Kazakhstan", "num_origins": "111471"}, - {"geo": "Belarus", "num_origins": "109161"}, - {"geo": "Sweden", "num_origins": "108230"}, - {"geo": "Switzerland", "num_origins": "106121"}, - {"geo": "Saudi Arabia", "num_origins": "100966"}, - {"geo": "Israel", "num_origins": "99539"}, - {"geo": "Algeria", "num_origins": "98160"}, - {"geo": "Morocco", "num_origins": "96973"}, - {"geo": "Ireland", "num_origins": "96613"}, - {"geo": "Hong Kong", "num_origins": "95717"}, - {"geo": "United Arab Emirates", "num_origins": "91116"}, - {"geo": "Croatia", "num_origins": "85514"}, - {"geo": "Venezuela (Bolivarian Republic of)", "num_origins": "84283"}, - {"geo": "Slovakia", "num_origins": "84177"}, - {"geo": "Finland", "num_origins": "83107"}, - {"geo": "Serbia", "num_origins": "80789"}, - {"geo": "Ecuador", "num_origins": "80083"}, - {"geo": "Bulgaria", "num_origins": "75818"}, - {"geo": "Denmark", "num_origins": "69550"}, - {"geo": "New Zealand", "num_origins": "68444"}, - {"geo": "Uzbekistan", "num_origins": "65735"}, - {"geo": "Iraq", "num_origins": "65305"}, - {"geo": "Kenya", "num_origins": "62330"}, - {"geo": "Nepal", "num_origins": "60371"}, - {"geo": "Norway", "num_origins": "58300"}, - {"geo": "China", "num_origins": "57495"}, - {"geo": "Bolivia (Plurinational State of)", "num_origins": "55245"}, - {"geo": "Tunisia", "num_origins": "54813"}, - {"geo": "Sri Lanka", "num_origins": "53879"}, - {"geo": "Guatemala", "num_origins": "50897"}, - {"geo": "Azerbaijan", "num_origins": "46317"}, - {"geo": "Kyrgyzstan", "num_origins": "45478"}, - {"geo": "Lithuania", "num_origins": "45215"}, - {"geo": "Costa Rica", "num_origins": "44736"}, - {"geo": "Dominican Republic", "num_origins": "42618"}, - {"geo": "Moldova, Republic of", "num_origins": "41976"}, - {"geo": "Bosnia and Herzegovina", "num_origins": "41953"}, - {"geo": "Jordan", "num_origins": "41773"}, - {"geo": "Uruguay", "num_origins": "41139"}, - {"geo": "Panama", "num_origins": "38437"}, - {"geo": "Slovenia", "num_origins": "36027"}, - {"geo": "Ghana", "num_origins": "35980"}, - {"geo": "Paraguay", "num_origins": "35415"}, - {"geo": "Georgia", "num_origins": "34921"}, - {"geo": "Qatar", "num_origins": "34403"}, - {"geo": "Lebanon", "num_origins": "33694"}, - {"geo": "Puerto Rico", "num_origins": "33617"}, - {"geo": "El Salvador", "num_origins": "31654"}, - {"geo": "Syrian Arab Republic", "num_origins": "30714"}, - {"geo": "Latvia", "num_origins": "30530"}, - {"geo": "Honduras", "num_origins": "29712"}, - {"geo": "Myanmar", "num_origins": "29348"}, - {"geo": "Cyprus", "num_origins": "29012"}, - {"geo": "Oman", "num_origins": "27345"}, - {"geo": "Tanzania, United Republic of", "num_origins": "27335"}, - {"geo": "Cameroon", "num_origins": "26828"}, - {"geo": "Kuwait", "num_origins": "26458"}, - {"geo": "Armenia", "num_origins": "26355"}, - {"geo": "Nicaragua", "num_origins": "26015"}, - {"geo": "Estonia", "num_origins": "25576"}, - {"geo": "Côte d'Ivoire", "num_origins": "25208"}, - {"geo": "Cambodia", "num_origins": "24593"}, - {"geo": "Uganda", "num_origins": "24532"}, - {"geo": "Libya", "num_origins": "23730"}, - {"geo": "Cuba", "num_origins": "23056"}, - {"geo": "Ethiopia", "num_origins": "22650"}, - {"geo": "Albania", "num_origins": "22445"}, - {"geo": "Yemen", "num_origins": "22186"}, - {"geo": "North Macedonia", "num_origins": "21259"}, - {"geo": "Palestine, State of", "num_origins": "20468"}, - {"geo": "Senegal", "num_origins": "20323"}, - {"geo": "Montenegro", "num_origins": "20212"}, - {"geo": "Sudan", "num_origins": "20152"}, - {"geo": "Jamaica", "num_origins": "18847"}, - {"geo": "Iceland", "num_origins": "18261"}, - {"geo": "Zambia", "num_origins": "17567"}, - {"geo": "Bahrain", "num_origins": "17522"}, - {"geo": "Réunion", "num_origins": "17251"}, - {"geo": "Trinidad and Tobago", "num_origins": "16445"}, - {"geo": "Mauritius", "num_origins": "16238"}, - {"geo": "Zimbabwe", "num_origins": "15515"}, - {"geo": "Tajikistan", "num_origins": "14835"}, - {"geo": "Lao People's Democratic Republic", "num_origins": "14796"}, - {"geo": "Luxembourg", "num_origins": "14647"}, - {"geo": "Congo, Democratic Republic of the", "num_origins": "14545"}, - {"geo": "Angola", "num_origins": "13428"}, - {"geo": "Haiti", "num_origins": "13083"}, - {"geo": "Malta", "num_origins": "12984"}, - {"geo": "Mozambique", "num_origins": "12706"}, - {"geo": "Mongolia", "num_origins": "12574"}, - {"geo": "Burkina Faso", "num_origins": "12325"}, - {"geo": "Benin", "num_origins": "12292"}, - {"geo": "Somalia", "num_origins": "12176"}, - {"geo": "Mali", "num_origins": "10834"}, - {"geo": "Turkmenistan", "num_origins": "10192"}, - {"geo": "Afghanistan", "num_origins": "9613"}, - {"geo": "Martinique", "num_origins": "9314"}, - {"geo": "Guadeloupe", "num_origins": "8961"}, - {"geo": "Brunei Darussalam", "num_origins": "8854"}, - {"geo": "Botswana", "num_origins": "8657"}, - {"geo": "Namibia", "num_origins": "8535"}, - {"geo": "Papua New Guinea", "num_origins": "8447"}, - {"geo": "Togo", "num_origins": "8308"}, - {"geo": "Malawi", "num_origins": "8305"}, - {"geo": "Maldives", "num_origins": "8262"}, - {"geo": "Kosovo", "num_origins": "7807"}, - {"geo": "Gabon", "num_origins": "7754"}, - {"geo": "Bhutan", "num_origins": "6919"}, - {"geo": "Guinea", "num_origins": "6702"}, - {"geo": "Madagascar", "num_origins": "6620"}, - {"geo": "Guyana", "num_origins": "6303"}, - {"geo": "Rwanda", "num_origins": "6129"}, - {"geo": "Mauritania", "num_origins": "5995"}, - {"geo": "Macao", "num_origins": "5889"}, - {"geo": "Suriname", "num_origins": "5827"}, - {"geo": "Niger", "num_origins": "5484"}, - {"geo": "Fiji", "num_origins": "5388"}, - {"geo": "Congo", "num_origins": "4697"}, - {"geo": "Barbados", "num_origins": "4509"}, - {"geo": "Bahamas", "num_origins": "4467"}, - {"geo": "Chad", "num_origins": "4426"}, - {"geo": "Sierra Leone", "num_origins": "4345"}, - {"geo": "Cabo Verde", "num_origins": "4125"}, - {"geo": "Liberia", "num_origins": "3899"}, - {"geo": "Belize", "num_origins": "3871"}, - {"geo": "French Guiana", "num_origins": "3603"}, - {"geo": "Eswatini", "num_origins": "3554"}, - {"geo": "French Polynesia", "num_origins": "3489"}, - {"geo": "New Caledonia", "num_origins": "3379"}, - {"geo": "Lesotho", "num_origins": "3265"}, - {"geo": "Gambia", "num_origins": "3217"}, - {"geo": "Timor-Leste", "num_origins": "3074"}, - {"geo": "Andorra", "num_origins": "3073"}, - {"geo": "South Sudan", "num_origins": "3040"}, - {"geo": "Curaçao", "num_origins": "2987"}, - {"geo": "Western Sahara", "num_origins": "2739"}, - {"geo": "Saint Lucia", "num_origins": "2493"}, - {"geo": "Guam", "num_origins": "2466"}, - {"geo": "Antigua and Barbuda", "num_origins": "2449"}, - {"geo": "Aruba", "num_origins": "2420"}, - {"geo": "Djibouti", "num_origins": "2395"}, - {"geo": "Burundi", "num_origins": "2301"}, - {"geo": "Seychelles", "num_origins": "2007"}, - {"geo": "Mayotte", "num_origins": "1820"}, - {"geo": "Grenada", "num_origins": "1597"}, - {"geo": "Guinea-Bissau", "num_origins": "1592"}, - {"geo": "Comoros", "num_origins": "1563"}, - {"geo": "Cayman Islands", "num_origins": "1549"}, - {"geo": "Jersey", "num_origins": "1499"}, - {"geo": "Saint Vincent and the Grenadines", "num_origins": "1453"}, - {"geo": "Isle of Man", "num_origins": "1374"}, - {"geo": "Faroe Islands", "num_origins": "1233"}, - {"geo": "Equatorial Guinea", "num_origins": "1218"}, - {"geo": "Virgin Islands (U.S.)", "num_origins": "1074"}, - {"geo": "Dominica", "num_origins": "1049"}, - {"geo": "Sint Maarten (Dutch part)", "num_origins": "952"}, - {"geo": "Solomon Islands", "num_origins": "946"}, - {"geo": "Guernsey", "num_origins": "936"}, - {"geo": "Saint Kitts and Nevis", "num_origins": "917"}, - {"geo": "Central African Republic", "num_origins": "879"}, - {"geo": "Virgin Islands (British)", "num_origins": "864"}, - {"geo": "San Marino", "num_origins": "845"}, - {"geo": "Bermuda", "num_origins": "796"}, - {"geo": "Samoa", "num_origins": "771"}, - {"geo": "Gibraltar", "num_origins": "710"}, - {"geo": "Vanuatu", "num_origins": "697"}, - {"geo": "Saint Martin (French part)", "num_origins": "642"}, - {"geo": "Greenland", "num_origins": "631"}, - {"geo": "Bonaire, Sint Eustatius and Saba", "num_origins": "615"}, - {"geo": "Marshall Islands", "num_origins": "604"}, - {"geo": "Turks and Caicos Islands", "num_origins": "548"}, -] diff --git a/functions/geos/main.py b/functions/geos/main.py deleted file mode 100644 index 0fd5e9d..0000000 --- a/functions/geos/main.py +++ /dev/null @@ -1,15 +0,0 @@ -import functions_framework - -from .libs.utils import ( COUNTRIES ) -from .libs.result import Result -from .libs.network import respond_cors, respond - -@functions_framework.http -def dispatcher(request): - - if request.method == "OPTIONS": - return respond_cors() - - response = Result(result=COUNTRIES) - - return respond(response) \ No newline at end of file diff --git a/functions/geos/requirements.txt b/functions/geos/requirements.txt deleted file mode 100644 index f336d0d..0000000 --- a/functions/geos/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -functions-framework -google-cloud-firestore -pytest \ No newline at end of file diff --git a/functions/lighthouse/libs/__init__.py b/functions/lighthouse/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/functions/lighthouse/libs/network.py b/functions/lighthouse/libs/network.py deleted file mode 100644 index d097aa8..0000000 --- a/functions/lighthouse/libs/network.py +++ /dev/null @@ -1,37 +0,0 @@ - -""" -Network - -Handles formatting responses to match the tuple pattern required by -the flask/GCP wrapper for Cloud Functions. -""" -import json -from .utils import convert_to_hashes - -PREFLIGHT_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Content-Type, Timing-Allow-Origin", - "Access-Control-Max-Age": "3600", - } - -HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - "cache-control": "public, max-age=21600", - "Timing-Allow-Origin": "*" - } - -def respond_cors(): - """ - To be used to return OPTIONS responses to satisfy CORS preflight requests. - """ - return ("", 204, PREFLIGHT_HEADERS) - -def respond(result, headers=HEADERS): - """ - To be used to return responses to satisfy CORS requests. - """ - status = 200 if result.success() else 400 - payload = result.result if result.success() else convert_to_hashes(result.errors) - return (json.dumps(payload), status, headers) diff --git a/functions/lighthouse/libs/queries.py b/functions/lighthouse/libs/queries.py deleted file mode 100644 index 09d33c9..0000000 --- a/functions/lighthouse/libs/queries.py +++ /dev/null @@ -1,43 +0,0 @@ -import os -from google.cloud import firestore -from google.cloud.firestore_v1.base_query import FieldFilter -from .result import Result -from .utils import convert_to_array - -DB = firestore.Client(project=os.environ.get('PROJECT'), database=os.environ.get('DATABASE')) -TABLE = 'lighthouse' - -def get_latest_date(): - """Retrieve the latest date in the collection.""" - query = DB.collection(TABLE).order_by('date', direction=firestore.Query.DESCENDING).limit(1) - docs = query.stream() - for doc in docs: - return doc.to_dict().get('date') - return None - -def list_data(params): - - technology_array = convert_to_array(params['technology']) - data = [] - - if 'start' in params and params['start'] == 'latest': - params['start'] = get_latest_date() - - for technology in technology_array: - query = DB.collection(TABLE) - - if 'start' in params: - query = query.where(filter=FieldFilter('date', '>=', params['start'])) - if 'end' in params: - query = query.where(filter=FieldFilter('date', '<=', params['end'])) - - query = query.where(filter=FieldFilter('geo', '==', params['geo'])) - query = query.where(filter=FieldFilter('rank', '==', params['rank'])) - query = query.where(filter=FieldFilter('technology', '==', technology)) - - documents = query.stream() - - for doc in documents: - data.append(doc.to_dict()) - - return Result(result=data) \ No newline at end of file diff --git a/functions/lighthouse/libs/result.py b/functions/lighthouse/libs/result.py deleted file mode 100644 index 63034b6..0000000 --- a/functions/lighthouse/libs/result.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Result(): - def __init__(self, status=None, result=None, errors=[]): - self._status = status - self.result = result - self.errors = errors - - def success(self) -> bool: - return not self.failure() - - def failure(self) -> bool: - return len(self.errors) > 0 - - @property - def status(self): - if self._status != None: - return self._status - - return "ok" if self.success else "error" - \ No newline at end of file diff --git a/functions/lighthouse/libs/utils.py b/functions/lighthouse/libs/utils.py deleted file mode 100644 index 29692f4..0000000 --- a/functions/lighthouse/libs/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -from urllib.parse import unquote - -def convert_to_hashes(arr): - hashes_arr = [] - for inner_arr in arr: - hash_dict = {inner_arr[0]: inner_arr[1]} - hashes_arr.append(hash_dict) - return hashes_arr - -def convert_to_array(data_string): - decoded_data = unquote(data_string) - list = decoded_data.split(',') - return list \ No newline at end of file diff --git a/functions/lighthouse/libs/validator.py b/functions/lighthouse/libs/validator.py deleted file mode 100644 index 4c202a4..0000000 --- a/functions/lighthouse/libs/validator.py +++ /dev/null @@ -1,27 +0,0 @@ -from .result import Result - -class Validator(): - def __init__(self, params): - self.params = params - self.errors = [] - self.normalizer_params = self.normalize(params) - - def validate(self): - result = Result(status="ok", result="()") - - if 'geo' not in self.params: - self.add_error("geo", "missing geo parameter") - - if 'technology' not in self.params: - self.add_error("technology", "missing technology parameter") - - if 'rank' not in self.params: - self.add_error("rank", "missing rank parameter") - - return Result(errors=self.errors, result=self.params) - - def add_error(self, key, error): - self.errors.append([key, error]) - - def normalize(self, params): - return "" diff --git a/functions/lighthouse/main.py b/functions/lighthouse/main.py deleted file mode 100644 index 3fab032..0000000 --- a/functions/lighthouse/main.py +++ /dev/null @@ -1,24 +0,0 @@ -import functions_framework - -from .libs.validator import Validator -from .libs.queries import list_data -from .libs.network import respond_cors, respond - -@functions_framework.http -def dispatcher(request): - - if request.method == "OPTIONS": - return respond_cors() - - args = request.args.to_dict() - - validator = Validator(params=args) - result = validator.validate() - - if result.failure(): - print("error", result.errors) - return respond(result) - - response = list_data(result.result) - - return respond(response) \ No newline at end of file diff --git a/functions/lighthouse/requirements.txt b/functions/lighthouse/requirements.txt deleted file mode 100644 index f336d0d..0000000 --- a/functions/lighthouse/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -functions-framework -google-cloud-firestore -pytest \ No newline at end of file diff --git a/functions/page-weight/libs/__init__.py b/functions/page-weight/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/functions/page-weight/libs/network.py b/functions/page-weight/libs/network.py deleted file mode 100644 index 76a731f..0000000 --- a/functions/page-weight/libs/network.py +++ /dev/null @@ -1,37 +0,0 @@ - -""" -Network - -Handles formatting responses to match the tuple pattern required by -the flask/GCP wrapper for Cloud Functions. -""" -import json -from .utils import convert_to_hashes - -PREFLIGHT_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Content-Type, Timing-Allow-Origin", - "Access-Control-Max-Age": "3600", - } - -HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - "cache-control": "public, max-age=21600", - "Timing-Allow-Origin": "*" - } - -def respond_cors(): - """ - To be used to return OPTIONS responses to satisfy CORS preflight requests. - """ - return ("", 204, PREFLIGHT_HEADERS) - -def respond(result, headers=HEADERS): - """ - To be used to return responses to satisfy CORS requests. - """ - status = 200 if result.success() else 400 - payload = result.result if result.success() else convert_to_hashes(result.errors) - return (json.dumps(payload), status, headers) \ No newline at end of file diff --git a/functions/page-weight/libs/queries.py b/functions/page-weight/libs/queries.py deleted file mode 100644 index 8d4d29d..0000000 --- a/functions/page-weight/libs/queries.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -from google.cloud import firestore -from google.cloud.firestore_v1.base_query import FieldFilter -from .result import Result -from .utils import convert_to_array - -DB = firestore.Client(project=os.environ.get('PROJECT'), database=os.environ.get('DATABASE')) -TABLE = 'page_weight' - -def get_latest_date(): - """Retrieve the latest date in the collection.""" - query = DB.collection(TABLE).order_by('date', direction=firestore.Query.DESCENDING).limit(1) - docs = query.stream() - for doc in docs: - return doc.to_dict().get('date') - return None - -def list_data(params): - - technology_array = convert_to_array(params['technology']) - data = [] - - if 'start' in params and params['start'] == 'latest': - params['start'] = get_latest_date() - - for technology in technology_array: - query = DB.collection(TABLE) - - if 'start' in params: - query = query.where(filter=FieldFilter('date', '>=', params['start'])) - - if 'end' in params: - query = query.where(filter=FieldFilter('date', '<=', params['end'])) - - if 'geo' in params: - query = query.where(filter=FieldFilter('geo', '==', params['geo'])) - - if 'rank' in params: - query = query.where(filter=FieldFilter('rank', '==', params['rank'])) - - query = query.where(filter=FieldFilter('technology', '==', technology)) - - documents = query.stream() - - for doc in documents: - data.append(doc.to_dict()) - - return Result(result=data) diff --git a/functions/page-weight/libs/result.py b/functions/page-weight/libs/result.py deleted file mode 100644 index 63034b6..0000000 --- a/functions/page-weight/libs/result.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Result(): - def __init__(self, status=None, result=None, errors=[]): - self._status = status - self.result = result - self.errors = errors - - def success(self) -> bool: - return not self.failure() - - def failure(self) -> bool: - return len(self.errors) > 0 - - @property - def status(self): - if self._status != None: - return self._status - - return "ok" if self.success else "error" - \ No newline at end of file diff --git a/functions/page-weight/libs/utils.py b/functions/page-weight/libs/utils.py deleted file mode 100644 index 997c85e..0000000 --- a/functions/page-weight/libs/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -from urllib.parse import unquote - -def convert_to_hashes(arr): - hashes_arr = [] - for inner_arr in arr: - hash_dict = {inner_arr[0]: inner_arr[1]} - hashes_arr.append(hash_dict) - return hashes_arr - -def convert_to_array(data_string): - decoded_data = unquote(data_string) - list = decoded_data.split(',') - return list diff --git a/functions/page-weight/libs/validator.py b/functions/page-weight/libs/validator.py deleted file mode 100644 index 4c202a4..0000000 --- a/functions/page-weight/libs/validator.py +++ /dev/null @@ -1,27 +0,0 @@ -from .result import Result - -class Validator(): - def __init__(self, params): - self.params = params - self.errors = [] - self.normalizer_params = self.normalize(params) - - def validate(self): - result = Result(status="ok", result="()") - - if 'geo' not in self.params: - self.add_error("geo", "missing geo parameter") - - if 'technology' not in self.params: - self.add_error("technology", "missing technology parameter") - - if 'rank' not in self.params: - self.add_error("rank", "missing rank parameter") - - return Result(errors=self.errors, result=self.params) - - def add_error(self, key, error): - self.errors.append([key, error]) - - def normalize(self, params): - return "" diff --git a/functions/page-weight/main.py b/functions/page-weight/main.py deleted file mode 100644 index 3fab032..0000000 --- a/functions/page-weight/main.py +++ /dev/null @@ -1,24 +0,0 @@ -import functions_framework - -from .libs.validator import Validator -from .libs.queries import list_data -from .libs.network import respond_cors, respond - -@functions_framework.http -def dispatcher(request): - - if request.method == "OPTIONS": - return respond_cors() - - args = request.args.to_dict() - - validator = Validator(params=args) - result = validator.validate() - - if result.failure(): - print("error", result.errors) - return respond(result) - - response = list_data(result.result) - - return respond(response) \ No newline at end of file diff --git a/functions/page-weight/requirements.txt b/functions/page-weight/requirements.txt deleted file mode 100644 index f336d0d..0000000 --- a/functions/page-weight/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -functions-framework -google-cloud-firestore -pytest \ No newline at end of file diff --git a/functions/ranks/libs/__init__.py b/functions/ranks/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/functions/ranks/libs/network.py b/functions/ranks/libs/network.py deleted file mode 100644 index d097aa8..0000000 --- a/functions/ranks/libs/network.py +++ /dev/null @@ -1,37 +0,0 @@ - -""" -Network - -Handles formatting responses to match the tuple pattern required by -the flask/GCP wrapper for Cloud Functions. -""" -import json -from .utils import convert_to_hashes - -PREFLIGHT_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Content-Type, Timing-Allow-Origin", - "Access-Control-Max-Age": "3600", - } - -HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - "cache-control": "public, max-age=21600", - "Timing-Allow-Origin": "*" - } - -def respond_cors(): - """ - To be used to return OPTIONS responses to satisfy CORS preflight requests. - """ - return ("", 204, PREFLIGHT_HEADERS) - -def respond(result, headers=HEADERS): - """ - To be used to return responses to satisfy CORS requests. - """ - status = 200 if result.success() else 400 - payload = result.result if result.success() else convert_to_hashes(result.errors) - return (json.dumps(payload), status, headers) diff --git a/functions/ranks/libs/result.py b/functions/ranks/libs/result.py deleted file mode 100644 index 63034b6..0000000 --- a/functions/ranks/libs/result.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Result(): - def __init__(self, status=None, result=None, errors=[]): - self._status = status - self.result = result - self.errors = errors - - def success(self) -> bool: - return not self.failure() - - def failure(self) -> bool: - return len(self.errors) > 0 - - @property - def status(self): - if self._status != None: - return self._status - - return "ok" if self.success else "error" - \ No newline at end of file diff --git a/functions/ranks/libs/utils.py b/functions/ranks/libs/utils.py deleted file mode 100644 index 568afae..0000000 --- a/functions/ranks/libs/utils.py +++ /dev/null @@ -1,16 +0,0 @@ -def convert_to_hashes(arr): - hashes_arr = [] - for inner_arr in arr: - hash_dict = {inner_arr[0]: inner_arr[1]} - hashes_arr.append(hash_dict) - return hashes_arr - - -RANKS = [ - {"num_origins": "9731427", "rank": "ALL"}, - {"num_origins": "7232806", "rank": "Top 10M"}, - {"num_origins": "881817", "rank": "Top 1M"}, - {"num_origins": "91410", "rank": "Top 100k"}, - {"num_origins": "9524", "rank": "Top 10k"}, - {"num_origins": "965", "rank": "Top 1k"}, -] diff --git a/functions/ranks/main.py b/functions/ranks/main.py deleted file mode 100644 index 0b9147b..0000000 --- a/functions/ranks/main.py +++ /dev/null @@ -1,15 +0,0 @@ -import functions_framework - -from .libs.utils import ( RANKS ) -from .libs.result import Result -from .libs.network import respond_cors, respond - -@functions_framework.http -def dispatcher(request): - - if request.method == "OPTIONS": - return respond_cors() - - response = Result(result=RANKS) - - return respond(response) \ No newline at end of file diff --git a/functions/ranks/requirements.txt b/functions/ranks/requirements.txt deleted file mode 100644 index f336d0d..0000000 --- a/functions/ranks/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -functions-framework -google-cloud-firestore -pytest \ No newline at end of file diff --git a/functions/technologies/libs/__init__.py b/functions/technologies/libs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/functions/technologies/libs/network.py b/functions/technologies/libs/network.py deleted file mode 100644 index d097aa8..0000000 --- a/functions/technologies/libs/network.py +++ /dev/null @@ -1,37 +0,0 @@ - -""" -Network - -Handles formatting responses to match the tuple pattern required by -the flask/GCP wrapper for Cloud Functions. -""" -import json -from .utils import convert_to_hashes - -PREFLIGHT_HEADERS = { - "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET", - "Access-Control-Allow-Headers": "Content-Type, Timing-Allow-Origin", - "Access-Control-Max-Age": "3600", - } - -HEADERS = { - "Access-Control-Allow-Origin": "*", - "Content-Type": "application/json", - "cache-control": "public, max-age=21600", - "Timing-Allow-Origin": "*" - } - -def respond_cors(): - """ - To be used to return OPTIONS responses to satisfy CORS preflight requests. - """ - return ("", 204, PREFLIGHT_HEADERS) - -def respond(result, headers=HEADERS): - """ - To be used to return responses to satisfy CORS requests. - """ - status = 200 if result.success() else 400 - payload = result.result if result.success() else convert_to_hashes(result.errors) - return (json.dumps(payload), status, headers) diff --git a/functions/technologies/libs/presenters.py b/functions/technologies/libs/presenters.py deleted file mode 100644 index 02c1a4d..0000000 --- a/functions/technologies/libs/presenters.py +++ /dev/null @@ -1,10 +0,0 @@ -class Presenters: - @staticmethod - def technology(item): - return { - 'technology': item['technology'], - 'category': item['category'], - 'description': item['description'], - 'icon': item['icon'], - 'origins': item['origins'] - } diff --git a/functions/technologies/libs/queries.py b/functions/technologies/libs/queries.py deleted file mode 100644 index 6868472..0000000 --- a/functions/technologies/libs/queries.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -import json -from google.cloud import firestore -from google.cloud.firestore_v1.base_query import FieldFilter, Or - -from .result import Result -from .utils import convert_to_array -from .presenters import Presenters - -DB = firestore.Client( - project=os.environ.get("PROJECT"), database=os.environ.get("DATABASE") -) - -def list_data(params): - ref = DB.collection("technologies") - - query = ref.order_by("technology", direction=firestore.Query.ASCENDING) - - if "technology" in params: - arfilters = [] - params_array = convert_to_array(params["technology"]) - for tech in params_array: - arfilters.append(FieldFilter("technology", "==", tech)) - query = query.where(filter=Or(filters=arfilters)) - - if "category" in params: - params_array = convert_to_array(params["category"]) - query = query.where( - filter=FieldFilter("category_obj", "array_contains_any", params_array) - ) - - documents = query.stream() - data = [] - - if "onlyname" in params: - appended_tech = set() - for doc in documents: - tech = doc.get("technology") - if tech not in appended_tech: - appended_tech.add(tech) - data.append(tech) - - else: - for doc in documents: - data.append(Presenters.technology(doc.to_dict())) - - return Result(result=data) diff --git a/functions/technologies/libs/result.py b/functions/technologies/libs/result.py deleted file mode 100644 index 63034b6..0000000 --- a/functions/technologies/libs/result.py +++ /dev/null @@ -1,20 +0,0 @@ - -class Result(): - def __init__(self, status=None, result=None, errors=[]): - self._status = status - self.result = result - self.errors = errors - - def success(self) -> bool: - return not self.failure() - - def failure(self) -> bool: - return len(self.errors) > 0 - - @property - def status(self): - if self._status != None: - return self._status - - return "ok" if self.success else "error" - \ No newline at end of file diff --git a/functions/technologies/libs/utils.py b/functions/technologies/libs/utils.py deleted file mode 100644 index 29692f4..0000000 --- a/functions/technologies/libs/utils.py +++ /dev/null @@ -1,14 +0,0 @@ -import json -from urllib.parse import unquote - -def convert_to_hashes(arr): - hashes_arr = [] - for inner_arr in arr: - hash_dict = {inner_arr[0]: inner_arr[1]} - hashes_arr.append(hash_dict) - return hashes_arr - -def convert_to_array(data_string): - decoded_data = unquote(data_string) - list = decoded_data.split(',') - return list \ No newline at end of file diff --git a/functions/technologies/libs/validator.py b/functions/technologies/libs/validator.py deleted file mode 100644 index 6a34617..0000000 --- a/functions/technologies/libs/validator.py +++ /dev/null @@ -1,21 +0,0 @@ -from .result import Result - -class Validator(): - def __init__(self, params): - self.params = params - self.errors = [] - self.normalizer_params = self.normalize(params) - - def validate(self): - result = Result(status="ok", result="()") - - # if 'technology' not in self.params: - # self.add_error("technology", "missing technology parameter") - - return Result(errors=self.errors, result=self.params) - - def add_error(self, key, error): - self.errors.append([key, error]) - - def normalize(self, params): - return "" diff --git a/functions/technologies/main.py b/functions/technologies/main.py deleted file mode 100644 index 3fab032..0000000 --- a/functions/technologies/main.py +++ /dev/null @@ -1,24 +0,0 @@ -import functions_framework - -from .libs.validator import Validator -from .libs.queries import list_data -from .libs.network import respond_cors, respond - -@functions_framework.http -def dispatcher(request): - - if request.method == "OPTIONS": - return respond_cors() - - args = request.args.to_dict() - - validator = Validator(params=args) - result = validator.validate() - - if result.failure(): - print("error", result.errors) - return respond(result) - - response = list_data(result.result) - - return respond(response) \ No newline at end of file diff --git a/functions/technologies/requirements.txt b/functions/technologies/requirements.txt deleted file mode 100644 index f336d0d..0000000 --- a/functions/technologies/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -functions-framework -google-cloud-firestore -pytest \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index da587a0..0000000 --- a/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -functions-framework -google-cloud-firestore -google-cloud-error-reporting -mock-firestore -pytest \ No newline at end of file diff --git a/src/__tests__/routes.test.js b/src/__tests__/routes.test.js new file mode 100644 index 0000000..90ac268 --- /dev/null +++ b/src/__tests__/routes.test.js @@ -0,0 +1,361 @@ +import request from 'supertest'; +import { jest } from '@jest/globals'; + +// Mock the entire utils/db module using ESM-compatible mocking +jest.unstable_mockModule('../utils/db.js', () => { + const mockDoc = { + data: () => ({ + technology: 'WordPress', + category: 'CMS', + description: 'A popular content management system', + icon: 'wordpress.svg', + origins: ['WordPress Foundation'], + rank: 'ALL', + geo: 'ALL', + date: '2023-01-01' + }), + get: (field) => { + const mockData = { + technology: 'WordPress', + category: 'CMS', + rank: 'ALL', + geo: 'ALL', + date: '2023-01-01' + }; + return mockData[field] || 'Mock Value'; + } + }; + + const mockQuerySnapshot = { + empty: false, + forEach: (callback) => { + callback(mockDoc); + }, + docs: [mockDoc] + }; + + // Create a chainable query mock - avoid infinite recursion + const mockQuery = { + where: jest.fn(), + orderBy: jest.fn(), + limit: jest.fn(), + select: jest.fn(), + get: jest.fn().mockResolvedValue(mockQuerySnapshot) + }; + + // Make the methods chainable by returning the same mock object + mockQuery.where.mockReturnValue(mockQuery); + mockQuery.orderBy.mockReturnValue(mockQuery); + mockQuery.limit.mockReturnValue(mockQuery); + mockQuery.select.mockReturnValue(mockQuery); + + const mockFirestoreInstance = { + collection: jest.fn().mockImplementation((collectionName) => mockQuery) + }; + + return { + firestore: mockFirestoreInstance, + firestoreOld: mockFirestoreInstance + }; +}); + +// Import app after mocking +const { app } = await import('../index.js'); + +describe('API Routes', () => { + describe('Health Check', () => { + it('should return a health check response', async () => { + const res = await request(app).get('/'); + expect(res.statusCode).toEqual(200); + expect(res.body).toHaveProperty('status', 'ok'); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + expect(res.headers['access-control-allow-methods']).toContain('GET'); + }); + }); + + describe('GET /v1/technologies', () => { + it('should return technologies', async () => { + const res = await request(app).get('/v1/technologies'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should filter technologies by name', async () => { + const res = await request(app).get('/v1/technologies?technology=WordPress'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should return only names when onlyname parameter is provided', async () => { + const res = await request(app).get('/v1/technologies?technology=WordPress&onlyname=true'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should support field selection', async () => { + const res = await request(app).get('/v1/technologies?technology=WordPress&fields=technology,icon'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should prioritize onlyname over fields parameter', async () => { + const res = await request(app).get('/v1/technologies?technology=WordPress&onlyname=true&fields=technology,icon'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/technologies') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('GET /v1/categories', () => { + it('should return categories', async () => { + const res = await request(app).get('/v1/categories'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should filter categories by name', async () => { + const res = await request(app).get('/v1/categories?category=CMS'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should return only names when onlyname parameter is provided', async () => { + const res = await request(app).get('/v1/categories?category=CMS&onlyname=true'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should support field selection', async () => { + const res = await request(app).get('/v1/categories?category=CMS&fields=category'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/categories') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('GET /v1/ranks', () => { + it('should return ranks', async () => { + const res = await request(app).get('/v1/ranks'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/ranks') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('GET /v1/geos', () => { + it('should return geos', async () => { + const res = await request(app).get('/v1/geos'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/geos') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('GET /v1/versions', () => { + it('should return versions', async () => { + const res = await request(app).get('/v1/versions'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should filter versions by technology', async () => { + const res = await request(app).get('/v1/versions?technology=WordPress'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/versions') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('GET /v1/adoption', () => { + it('should return adoption data with required parameters', async () => { + const res = await request(app).get('/v1/adoption?technology=WordPress&geo=ALL&rank=ALL&start=latest'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle missing required parameters', async () => { + const res = await request(app).get('/v1/adoption'); + expect(res.statusCode).toEqual(400); + expect(res.body).toHaveProperty('errors'); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/adoption') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('GET /v1/cwv', () => { + it('should return CWV data with required parameters', async () => { + const res = await request(app).get('/v1/cwv?technology=WordPress&geo=ALL&rank=ALL&start=latest'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle missing required parameters', async () => { + const res = await request(app).get('/v1/cwv'); + expect(res.statusCode).toEqual(400); + expect(res.body).toHaveProperty('errors'); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/cwv') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('GET /v1/lighthouse', () => { + it('should return lighthouse data with required parameters', async () => { + const res = await request(app).get('/v1/lighthouse?technology=WordPress&geo=ALL&rank=ALL&start=latest'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle missing required parameters', async () => { + const res = await request(app).get('/v1/lighthouse'); + expect(res.statusCode).toEqual(400); + expect(res.body).toHaveProperty('errors'); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/lighthouse') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('GET /v1/page-weight', () => { + it('should return page weight data with required parameters', async () => { + const res = await request(app).get('/v1/page-weight?technology=WordPress&geo=ALL&rank=ALL&start=latest'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + + it('should handle missing required parameters', async () => { + const res = await request(app).get('/v1/page-weight'); + expect(res.statusCode).toEqual(400); + expect(res.body).toHaveProperty('errors'); + }); + + it('should handle CORS preflight requests', async () => { + const res = await request(app) + .options('/v1/page-weight') + .set('Origin', 'http://example.com') + .set('Access-Control-Request-Method', 'GET') + .set('Access-Control-Request-Headers', 'Content-Type'); + + expect(res.statusCode).toEqual(204); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + }); + }); + + describe('Error Handling', () => { + it('should return 404 for unknown endpoints', async () => { + const res = await request(app).get('/v1/unknown-endpoint'); + expect(res.statusCode).toEqual(404); + expect(res.body).toHaveProperty('error', 'Not Found'); + }); + + it('should handle invalid query parameters gracefully', async () => { + const res = await request(app).get('/v1/technologies?invalid=parameter'); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBe(true); + }); + }); + + describe('Response Headers', () => { + it('should include proper CORS headers', async () => { + const res = await request(app).get('/v1/technologies'); + expect(res.headers['access-control-allow-origin']).toEqual('*'); + expect(res.headers['content-type']).toContain('application/json'); + expect(res.headers['cache-control']).toContain('public'); + }); + + it('should include ETag headers for caching', async () => { + const res = await request(app).get('/'); + expect(res.headers).toHaveProperty('etag'); + }); + + it('should include timing headers', async () => { + const res = await request(app).get('/v1/technologies'); + expect(res.headers['timing-allow-origin']).toEqual('*'); + }); + }); +}); diff --git a/src/controllers/adoptionController.js b/src/controllers/adoptionController.js new file mode 100644 index 0000000..b06b5d9 --- /dev/null +++ b/src/controllers/adoptionController.js @@ -0,0 +1,111 @@ +import { firestoreOld } from '../utils/db.js'; +const firestore = firestoreOld; + +import { + getLatestDate, + generateQueryCacheKey, + getCachedQueryResult, + setCachedQueryResult +} from '../utils/controllerHelpers.js'; + +const TABLE = 'adoption'; + +/** + * List adoption data with filtering + */ +const listAdoptionData = async (req, res) => { + try { + const params = req.query; + + // Validate required parameters inline for speed + if (!params.geo || !params.rank || !params.technology) { + res.statusCode = 400; + res.end(JSON.stringify({ + success: false, + errors: [ + ...(!params.geo ? [{ geo: 'missing geo parameter' }] : []), + ...(!params.rank ? [{ rank: 'missing rank parameter' }] : []), + ...(!params.technology ? [{ technology: 'missing technology parameter' }] : []) + ] + })); + return; + } + + // Fast preprocessing - handle 'latest' date and technology array + const techArray = params.technology ? decodeURIComponent(params.technology).split(',') : []; + + let startDate = params.start; + if (startDate === 'latest') { + startDate = await getLatestDate(firestore, TABLE); + } + + // Create cache key for this specific query + const queryFilters = { + geo: params.geo, + rank: params.rank, + technology: techArray, + startDate: startDate, + endDate: params.end + }; + const cacheKey = generateQueryCacheKey(TABLE, queryFilters); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + // Build query + let query = firestore.collection(TABLE); + + // Apply required filters + query = query.where('geo', '==', params.geo); + query = query.where('rank', '==', params.rank); + + // Apply technology filter + if (techArray.length <= 30) { + // Use 'in' operator for batch processing (Firestore limit: 30 values https://cloud.google.com/firestore/docs/query-data/queries#limits_on_or_queries) + query = query.where('technology', 'in', techArray); + } else { + res.statusCode = 400; + res.end(JSON.stringify({ + success: false, + errors: [{ technology: 'Too many technologies specified. Maximum 30 allowed.' }] + })); + return; + } + + // Apply date filters + if (startDate) query = query.where('date', '>=', startDate); + if (params.end) query = query.where('date', '<=', params.end); + + // Apply field projection to exclude geo/rank + query = query.select('date', 'technology', 'adoption'); + + // Execute query + const snapshot = await query.get(); + const data = []; + snapshot.forEach(doc => { + data.push(doc.data()); + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + // Direct response + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + console.error('Error fetching adoption data:', error); + res.statusCode = 500; + res.end(JSON.stringify({ + errors: [{ error: 'Failed to fetch adoption data' }] + })); + } +}; + +export { + listAdoptionData +}; diff --git a/src/controllers/categoriesController.js b/src/controllers/categoriesController.js new file mode 100644 index 0000000..66170cb --- /dev/null +++ b/src/controllers/categoriesController.js @@ -0,0 +1,80 @@ +import { firestore } from '../utils/db.js'; +import { + applyArrayFilter, + selectFields, + generateQueryCacheKey, + getCachedQueryResult, + setCachedQueryResult +} from '../utils/controllerHelpers.js'; + +/** + * List categories with optional filtering and field selection - Optimized version + */ +const listCategories = async (req, res) => { + try { + const params = req.query; + const isOnlyNames = params.onlyname || typeof params.onlyname === 'string'; + const hasCustomFields = params.fields && !isOnlyNames; + + // Create cache key for this specific query + const queryFilters = { + category: params.category, + onlyname: isOnlyNames, + fields: params.fields + }; + const cacheKey = generateQueryCacheKey('categories', queryFilters); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + let query = firestore.collection('categories').orderBy('category', 'asc'); + + // Apply category filter using shared utility + query = applyArrayFilter(query, 'category', params.category); + + if (isOnlyNames) { + // Only select category field for names-only queries + query = query.select('category'); + } else if (hasCustomFields) { + // Select only requested fields + const requestedFields = params.fields.split(',').map(f => f.trim()); + query = query.select(...requestedFields); + } + + // Execute query + const snapshot = await query.get(); + const data = []; + + // Process results based on response type + snapshot.forEach(doc => { + const docData = doc.data(); + + if (isOnlyNames) { + data.push(docData.category); + } else { + // Data already filtered by select(), just return it + data.push(docData); + } + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + // Direct response + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + console.error('Error fetching categories:', error); + res.statusCode = 500; + res.end(JSON.stringify({ + errors: [{ error: 'Failed to fetch categories' }] + })); + } +}; + +export { listCategories }; diff --git a/src/controllers/cwvtechController.js b/src/controllers/cwvtechController.js new file mode 100644 index 0000000..35a51fc --- /dev/null +++ b/src/controllers/cwvtechController.js @@ -0,0 +1,112 @@ +import { firestoreOld } from '../utils/db.js'; +const firestore = firestoreOld; + +import { + getLatestDate, + generateQueryCacheKey, + getCachedQueryResult, + setCachedQueryResult +} from '../utils/controllerHelpers.js'; + +const TABLE = 'core_web_vitals'; + +/** + * List Core Web Vitals data with filtering + */ +const listCWVTechData = async (req, res) => { + try { + const params = req.query; + + // Validate required parameters inline for speed + if (!params.geo || !params.rank || !params.technology) { + res.statusCode = 400; + res.end(JSON.stringify({ + success: false, + errors: [ + ...(!params.geo ? [{ geo: 'missing geo parameter' }] : []), + ...(!params.rank ? [{ rank: 'missing rank parameter' }] : []), + ...(!params.technology ? [{ technology: 'missing technology parameter' }] : []) + ] + })); + return; + } + + // Fast preprocessing - handle 'latest' date and technology array + const techArray = params.technology ? decodeURIComponent(params.technology).split(',') : []; + + let startDate = params.start; + if (startDate === 'latest') { + startDate = await getLatestDate(firestore, TABLE); + } + + // Create cache key for this specific query + const queryFilters = { + geo: params.geo, + rank: params.rank, + technology: techArray, + startDate: startDate, + endDate: params.end + }; + const cacheKey = generateQueryCacheKey(TABLE, queryFilters); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + // Build query + let query = firestore.collection(TABLE); + + // Apply required filters + query = query.where('geo', '==', params.geo); + query = query.where('rank', '==', params.rank); + + + // Apply technology filter + if (techArray.length <= 30) { + // Use 'in' operator for batch processing (Firestore limit: 30 values) + query = query.where('technology', 'in', techArray); + } else { + res.statusCode = 400; + res.end(JSON.stringify({ + success: false, + errors: [{ technology: 'Too many technologies specified. Maximum 30 allowed.' }] + })); + return; + } + + // Apply date filters + if (startDate) query = query.where('date', '>=', startDate); + if (params.end) query = query.where('date', '<=', params.end); + + // Apply field projection to exclude geo/rank + query = query.select('date', 'technology', 'vitals'); + + // Execute query + const snapshot = await query.get(); + const data = []; + snapshot.forEach(doc => { + data.push(doc.data()); + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + // Direct response without wrapper functions + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + console.error('Error fetching Core Web Vitals data:', error); + res.statusCode = 500; + res.end(JSON.stringify({ + errors: [{ error: 'Failed to fetch Core Web Vitals data' }] + })); + } +}; + +export { + listCWVTechData +}; diff --git a/src/controllers/geosController.js b/src/controllers/geosController.js new file mode 100644 index 0000000..31c8581 --- /dev/null +++ b/src/controllers/geosController.js @@ -0,0 +1,40 @@ +import { firestore } from '../utils/db.js'; +import { handleControllerError, generateQueryCacheKey, getCachedQueryResult, setCachedQueryResult } from '../utils/controllerHelpers.js'; + +/** + * List all geographic locations from database + */ +const listGeos = async (req, res) => { + try { + // Generate cache key for this query + const cacheKey = generateQueryCacheKey('geos', { orderBy: 'mobile_origins' }); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + const snapshot = await firestore.collection('geos').orderBy('mobile_origins', 'desc').select('geo').get(); + const data = []; + + // Extract only the 'geo' property from each document + snapshot.forEach(doc => { + data.push(doc.data()); + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + handleControllerError(res, error, 'fetching geographic locations'); + } +}; + +export { + listGeos +}; diff --git a/src/controllers/lighthouseController.js b/src/controllers/lighthouseController.js new file mode 100644 index 0000000..e7f0d49 --- /dev/null +++ b/src/controllers/lighthouseController.js @@ -0,0 +1,111 @@ +import { firestoreOld } from '../utils/db.js'; +const firestore = firestoreOld; + +import { + getLatestDate, + generateQueryCacheKey, + getCachedQueryResult, + setCachedQueryResult +} from '../utils/controllerHelpers.js'; + +const TABLE = 'lighthouse'; + +/** + * List Lighthouse data with filtering + */ +const listLighthouseData = async (req, res) => { + try { + const params = req.query; + + // Validate required parameters inline for speed + if (!params.geo || !params.rank || !params.technology) { + res.statusCode = 400; + res.end(JSON.stringify({ + success: false, + errors: [ + ...(!params.geo ? [{ geo: 'missing geo parameter' }] : []), + ...(!params.rank ? [{ rank: 'missing rank parameter' }] : []), + ...(!params.technology ? [{ technology: 'missing technology parameter' }] : []) + ] + })); + return; + } + + // Fast preprocessing - handle 'latest' date and technology array + const techArray = params.technology ? decodeURIComponent(params.technology).split(',') : []; + + let startDate = params.start; + if (startDate === 'latest') { + startDate = await getLatestDate(firestore, TABLE); + } + + // Create cache key for this specific query + const queryFilters = { + geo: params.geo, + rank: params.rank, + technology: techArray, + startDate: startDate, + endDate: params.end + }; + const cacheKey = generateQueryCacheKey(TABLE, queryFilters); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + // Build query + let query = firestore.collection(TABLE); + + // Apply required filters + query = query.where('geo', '==', params.geo); + query = query.where('rank', '==', params.rank); + + // Apply technology filter + if (techArray.length <= 30) { + // Use 'in' operator for batch processing (Firestore limit: 30 values) + query = query.where('technology', 'in', techArray); + } else { + res.statusCode = 400; + res.end(JSON.stringify({ + success: false, + errors: [{ technology: 'Too many technologies specified. Maximum 30 allowed.' }] + })); + return; + } + + // Apply date filters + if (startDate) query = query.where('date', '>=', startDate); + if (params.end) query = query.where('date', '<=', params.end); + + // Apply field projection to exclude geo/rank + query = query.select('date', 'technology', 'lighthouse'); + + // Execute query + const snapshot = await query.get(); + const data = []; + snapshot.forEach(doc => { + data.push(doc.data()); + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + // Direct response without wrapper functions + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + console.error('Error fetching Lighthouse data:', error); + res.statusCode = 500; + res.end(JSON.stringify({ + errors: [{ error: 'Failed to fetch Lighthouse data' }] + })); + } +}; + +export { + listLighthouseData +}; diff --git a/src/controllers/pageWeightController.js b/src/controllers/pageWeightController.js new file mode 100644 index 0000000..7fd6ff7 --- /dev/null +++ b/src/controllers/pageWeightController.js @@ -0,0 +1,111 @@ +import { firestoreOld } from '../utils/db.js'; +const firestore = firestoreOld; + +import { + getLatestDate, + generateQueryCacheKey, + getCachedQueryResult, + setCachedQueryResult +} from '../utils/controllerHelpers.js'; + +const TABLE = 'page_weight'; + +/** + * List Page Weight data with filtering + */ +const listPageWeightData = async (req, res) => { + try { + const params = req.query; + + // Validate required parameters inline for speed + if (!params.geo || !params.rank || !params.technology) { + res.statusCode = 400; + res.end(JSON.stringify({ + success: false, + errors: [ + ...(!params.geo ? [{ geo: 'missing geo parameter' }] : []), + ...(!params.rank ? [{ rank: 'missing rank parameter' }] : []), + ...(!params.technology ? [{ technology: 'missing technology parameter' }] : []) + ] + })); + return; + } + + // Fast preprocessing - handle 'latest' date and technology array + const techArray = params.technology ? decodeURIComponent(params.technology).split(',') : []; + + let startDate = params.start; + if (startDate === 'latest') { + startDate = await getLatestDate(firestore, TABLE); + } + + // Create cache key for this specific query + const queryFilters = { + geo: params.geo, + rank: params.rank, + technology: techArray, + startDate: startDate, + endDate: params.end + }; + const cacheKey = generateQueryCacheKey(TABLE, queryFilters); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + // Build query + let query = firestore.collection(TABLE); + + // Apply required filters + query = query.where('geo', '==', params.geo); + query = query.where('rank', '==', params.rank); + + // Apply technology filter + if (techArray.length <= 30) { + // Use 'in' operator for batch processing (Firestore limit: 30 values) + query = query.where('technology', 'in', techArray); + } else { + res.statusCode = 400; + res.end(JSON.stringify({ + success: false, + errors: [{ technology: 'Too many technologies specified. Maximum 30 allowed.' }] + })); + return; + } + + // Apply date filters + if (startDate) query = query.where('date', '>=', startDate); + if (params.end) query = query.where('date', '<=', params.end); + + // Apply field projection to exclude geo/rank and select only needed fields + query = query.select('date', 'technology', 'pageWeight'); + + // Execute query + const snapshot = await query.get(); + const data = []; + snapshot.forEach(doc => { + data.push(doc.data()); + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + // Direct response without wrapper functions + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + console.error('Error fetching Page Weight data:', error); + res.statusCode = 500; + res.end(JSON.stringify({ + errors: [{ error: 'Failed to fetch Page Weight data' }] + })); + } +}; + +export { + listPageWeightData +}; diff --git a/src/controllers/ranksController.js b/src/controllers/ranksController.js new file mode 100644 index 0000000..4fa7635 --- /dev/null +++ b/src/controllers/ranksController.js @@ -0,0 +1,39 @@ +import { firestore } from '../utils/db.js'; +import { handleControllerError, generateQueryCacheKey, getCachedQueryResult, setCachedQueryResult } from '../utils/controllerHelpers.js'; + +/** + * List all rank options from database + */ +const listRanks = async (req, res) => { + try { + // Generate cache key for this query + const cacheKey = generateQueryCacheKey('ranks', { orderBy: 'mobile_origins' }); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + const snapshot = await firestore.collection('ranks').orderBy('mobile_origins', 'desc').select('rank').get(); + const data = []; + + snapshot.forEach(doc => { + data.push(doc.data()); + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + handleControllerError(res, error, 'fetching ranks'); + } +}; + +export { + listRanks +}; diff --git a/src/controllers/technologiesController.js b/src/controllers/technologiesController.js new file mode 100644 index 0000000..0ed4a32 --- /dev/null +++ b/src/controllers/technologiesController.js @@ -0,0 +1,87 @@ +import { firestore } from '../utils/db.js'; +import { + applyArrayFilter, + selectFields, + generateQueryCacheKey, + getCachedQueryResult, + setCachedQueryResult +} from '../utils/controllerHelpers.js'; + +/** + * List technologies with optional filtering and field selection + */ +const listTechnologies = async (req, res) => { + try { + const params = req.query; + const isOnlyNames = params.onlyname || typeof params.onlyname === 'string'; + const hasCustomFields = params.fields && !isOnlyNames; + + // Create cache key for this specific query + const queryFilters = { + technology: params.technology, + category: params.category, + onlyname: isOnlyNames, + fields: params.fields + }; + const cacheKey = generateQueryCacheKey('technologies', queryFilters); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + let query = firestore.collection('technologies').orderBy('technology', 'asc'); + + // Apply filters using shared utilities + query = applyArrayFilter(query, 'technology', params.technology); + query = applyArrayFilter(query, 'category_obj', params.category, 'array-contains-any'); + + if (isOnlyNames) { + // Only select technology field for names-only queries + query = query.select('technology'); + } else if (hasCustomFields) { + // Select only requested fields + const requestedFields = params.fields.split(',').map(f => f.trim()); + query = query.select(...requestedFields); + } else { + // Select default presentation fields + query = query.select('technology', 'category', 'description', 'icon', 'origins'); + } + + // Execute query + const snapshot = await query.get(); + let data = []; + + // Process results based on response type + snapshot.forEach(doc => { + const docData = doc.data(); + + if (isOnlyNames) { + data.push(docData.technology); + } else { + // Data already filtered by select(), just return it + data.push(docData) + } + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + // Direct response + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + console.error('Error fetching technologies:', error); + res.statusCode = 500; + res.end(JSON.stringify({ + errors: [{ error: 'Failed to fetch technologies' }] + })); + } +}; + +export { + listTechnologies +}; diff --git a/src/controllers/versionsController.js b/src/controllers/versionsController.js new file mode 100644 index 0000000..5c33bc9 --- /dev/null +++ b/src/controllers/versionsController.js @@ -0,0 +1,82 @@ +import { firestore } from '../utils/db.js'; +import { convertToArray } from '../utils/helpers.js'; +import { handleControllerError, generateQueryCacheKey, getCachedQueryResult, setCachedQueryResult } from '../utils/controllerHelpers.js'; + +/** + * List versions with optional technology filtering + */ +const listVersions = async (req, res) => { + try { + const params = req.query; + + // Generate cache key for this query + const cacheKey = generateQueryCacheKey('versions', params); + + // Check cache first + const cachedResult = getCachedQueryResult(cacheKey); + if (cachedResult) { + res.statusCode = 200; + res.end(JSON.stringify(cachedResult)); + return; + } + + let query = firestore.collection('versions'); + + // Apply technology filter - optimize for multiple technologies + if (params.technology) { + const technologies = convertToArray(params.technology); + if (technologies.length <= 30) { + // Use single query with 'in' operator for up to 30 technologies (Firestore limit) + query = query.where('technology', 'in', technologies); + } else { + // For more than 30 technologies, split into multiple queries and run in parallel + const chunks = []; + for (let i = 0; i < technologies.length; i += 30) { + chunks.push(technologies.slice(i, i + 30)); + } + + const promises = chunks.map(chunk => + firestore.collection('versions').where('technology', 'in', chunk).get() + ); + + const snapshots = await Promise.all(promises); + const data = []; + + snapshots.forEach(snapshot => { + snapshot.forEach(doc => { + data.push(doc.data()); + }); + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + res.statusCode = 200; + res.end(JSON.stringify(data)); + return; + } + } + + // Execute single query + const snapshot = await query.get(); + const data = []; + + // Extract all version documents + snapshot.forEach(doc => { + data.push(doc.data()); + }); + + // Cache the result + setCachedQueryResult(cacheKey, data); + + // Send response + res.statusCode = 200; + res.end(JSON.stringify(data)); + } catch (error) { + handleControllerError(res, error, 'fetching versions'); + } +}; + +export { + listVersions +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..897e782 --- /dev/null +++ b/src/index.js @@ -0,0 +1,191 @@ +import http from 'http'; +import url from 'url'; +import crypto from 'crypto'; +import functions from '@google-cloud/functions-framework'; + +// Dynamic imports for better performance - only load when needed +const controllers = { + technologies: null, + categories: null, + adoption: null, + cwvtech: null, + lighthouse: null, + pageWeight: null, + ranks: null, + geos: null, + versions: null +}; + +// Helper function to dynamically import controllers +const getController = async (name) => { + if (!controllers[name]) { + switch (name) { + case 'technologies': + controllers[name] = await import('./controllers/technologiesController.js'); + break; + case 'categories': + controllers[name] = await import('./controllers/categoriesController.js'); + break; + case 'adoption': + controllers[name] = await import('./controllers/adoptionController.js'); + break; + case 'cwvtech': + controllers[name] = await import('./controllers/cwvtechController.js'); + break; + case 'lighthouse': + controllers[name] = await import('./controllers/lighthouseController.js'); + break; + case 'pageWeight': + controllers[name] = await import('./controllers/pageWeightController.js'); + break; + case 'ranks': + controllers[name] = await import('./controllers/ranksController.js'); + break; + case 'geos': + controllers[name] = await import('./controllers/geosController.js'); + break; + case 'versions': + controllers[name] = await import('./controllers/versionsController.js'); + break; + } + } + return controllers[name]; +}; + +// Helper function to set CORS headers +const setCORSHeaders = (res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Timing-Allow-Origin'); + res.setHeader('Access-Control-Max-Age', '86400'); +}; + +// Helper function to set common response headers +const setCommonHeaders = (res) => { + setCORSHeaders(res); + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Cache-Control', 'public, max-age=21600'); + res.setHeader('Timing-Allow-Origin', '*'); +}; + +// Helper function to generate ETag +const generateETag = (data) => { + return crypto.createHash('md5').update(JSON.stringify(data)).digest('hex'); +}; + +// Helper function to send JSON response with ETag support +const sendJSONResponse = (res, data, statusCode = 200) => { + const jsonData = JSON.stringify(data); + const etag = generateETag(data); + + res.setHeader('ETag', `"${etag}"`); + res.statusCode = statusCode; + res.end(jsonData); +}; + +// Helper function to check if resource is modified +const isModified = (req, etag) => { + const ifNoneMatch = req.headers['if-none-match']; + return !ifNoneMatch || ifNoneMatch !== `"${etag}"`; +}; + +// Helper function to parse query parameters +const parseQuery = (queryString) => { + const params = new URLSearchParams(queryString); + const result = {}; + for (const [key, value] of params) { + result[key] = value; + } + return result; +}; + +// Route handler function +const handleRequest = async (req, res) => { + try { + setCommonHeaders(res); + + // Handle OPTIONS requests for CORS preflight + if (req.method === 'OPTIONS') { + res.statusCode = 204; + res.end(); + return; + } + + // Parse URL + const parsedUrl = url.parse(req.url, true); + const pathname = parsedUrl.pathname; + const query = parsedUrl.query; + + // Add query to req object for compatibility with existing controllers + req.query = query; + + // Route handling + if (pathname === '/' && req.method === 'GET') { + // Health check endpoint + const data = { status: 'ok' }; + sendJSONResponse(res, data); + } else if (pathname === '/v1/technologies' && req.method === 'GET') { + const { listTechnologies } = await getController('technologies'); + await listTechnologies(req, res); + } else if (pathname === '/v1/categories' && req.method === 'GET') { + const { listCategories } = await getController('categories'); + await listCategories(req, res); + } else if (pathname === '/v1/adoption' && req.method === 'GET') { + const { listAdoptionData } = await getController('adoption'); + await listAdoptionData(req, res); + } else if (pathname === '/v1/cwv' && req.method === 'GET') { + const { listCWVTechData } = await getController('cwvtech'); + await listCWVTechData(req, res); + } else if (pathname === '/v1/lighthouse' && req.method === 'GET') { + const { listLighthouseData } = await getController('lighthouse'); + await listLighthouseData(req, res); + } else if (pathname === '/v1/page-weight' && req.method === 'GET') { + const { listPageWeightData } = await getController('pageWeight'); + await listPageWeightData(req, res); + } else if (pathname === '/v1/ranks' && req.method === 'GET') { + const { listRanks } = await getController('ranks'); + await listRanks(req, res); + } else if (pathname === '/v1/geos' && req.method === 'GET') { + const { listGeos } = await getController('geos'); + await listGeos(req, res); + } else if (pathname === '/v1/versions' && req.method === 'GET') { + const { listVersions } = await getController('versions'); + await listVersions(req, res); + } else if (pathname === '/v1/cache-stats' && req.method === 'GET') { + // Cache monitoring endpoint + const { getCacheStats } = await import('./utils/controllerHelpers.js'); + const stats = getCacheStats(); + sendJSONResponse(res, stats); + } else { + // 404 Not Found + res.statusCode = 404; + res.end(JSON.stringify({ error: 'Not Found' })); + } + } catch (error) { + console.error('Error:', error); + res.statusCode = 400; + res.end(JSON.stringify({ + errors: [{ error: error.message || 'Unknown error occurred' }] + })); + } +}; + +// Create HTTP server +const server = http.createServer(handleRequest); + +// Export the server for testing +export { server as app }; + +// Register with Functions Framework for Cloud Functions +functions.http('app', handleRequest); + +// For standalone server mode (local development) +// Note: In ES modules, there's no require.main === module equivalent +// We'll use import.meta.url to check if this is the main module +const isMain = import.meta.url === `file://${process.argv[1]}`; +if (isMain) { + const PORT = process.env.PORT || 3000; + server.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + }); +} diff --git a/src/package-lock.json b/src/package-lock.json new file mode 100644 index 0000000..89b6733 --- /dev/null +++ b/src/package-lock.json @@ -0,0 +1,6809 @@ +{ + "name": "tech-report-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tech-report-api", + "version": "1.0.0", + "dependencies": { + "@google-cloud/firestore": "7.3.0", + "@google-cloud/functions-framework": "^4.0.0" + }, + "devDependencies": { + "@jest/transform": "^30.0.0-beta.3", + "jest": "29.7.0", + "nodemon": "3.0.1", + "supertest": "^7.1.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", + "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", + "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helpers": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", + "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", + "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", + "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@google-cloud/firestore": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.3.0.tgz", + "integrity": "sha512-2IftQLAbCuVp0nTd3neeu+d3OYIegJpV/V9R4USQj51LzJcXPe8h8jZ7j3+svSNhJVGy6JsN0T1QqlJdMDhTwg==", + "license": "Apache-2.0", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.0.4", + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/functions-framework": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/functions-framework/-/functions-framework-4.0.0.tgz", + "integrity": "sha512-CNcYrz0/hw35Oq0D9RipHUB8KzH4ixq7o12L//qoOg0TFYv4953KrzCo0L2VP++19P39RShKTftDKMFmQhCeEw==", + "license": "Apache-2.0", + "dependencies": { + "@types/express": "^4.17.21", + "body-parser": "1.20.3", + "cloudevents": "^8.0.2", + "express": "^4.21.2", + "minimist": "^1.2.8", + "on-finished": "^2.3.0", + "read-package-up": "^11.0.0", + "semver": "^7.7.1" + }, + "bin": { + "functions-framework": "build/src/main.js", + "functions-framework-nodejs": "build/src/main.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/functions-framework/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.3.tgz", + "integrity": "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.0-beta.3.tgz", + "integrity": "sha512-IuB9mweyJI5ToVBRdptKb2w97LGnNHFI+V9/cGaYeFareL7BYD6KiUH022OC51K1841c6YzgYjyQmJHFxELZSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.0-beta.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.0-beta.3.tgz", + "integrity": "sha512-kiDaZ35ogPivxgLEGJ1jNW2KBtvmPwGlPjy5ASHiVE3kjn3g80galEIcWC0hZV6g5BtTx15VKzSyfOTiKXPnxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.0-beta.3.tgz", + "integrity": "sha512-2gixxaYdRh3MQaRsEenHejw0qBIW72DfwG1q9HPLXpnLkm5TKZlTOvOS33S00PGEoa4UG1Iq9tNHh7fxOJAGwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "30.0.0-beta.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^7.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "30.0.0-beta.3", + "jest-regex-util": "30.0.0-beta.3", + "jest-util": "30.0.0-beta.3", + "micromatch": "^4.0.8", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/schemas": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.0-beta.3.tgz", + "integrity": "sha512-tiT79EKOlJGT5v8fYr9UKLSyjlA3Ek+nk0cVZwJGnRqVp26EQSOTYXBCzj0dGMegkgnPTt3f7wP1kGGI8q/e0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@jest/types": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.0-beta.3.tgz", + "integrity": "sha512-x7GyHD8rxZ4Ygmp4rea3uPDIPZ6Jglcglaav8wQNqXsVUAByapDwLF52Cp3wEYMPMnvH4BicEj56j8fqZx5jng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.0-beta.3", + "@jest/schemas": "30.0.0-beta.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/@jest/transform/node_modules/@sinclair/typebox": { + "version": "0.34.33", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.33.tgz", + "integrity": "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/transform/node_modules/babel-plugin-istanbul": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", + "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", + "dev": true, + "license": "BSD-3-Clause", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/transform/node_modules/ci-info": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", + "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/jest-haste-map": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.0-beta.3.tgz", + "integrity": "sha512-MafsVPIca9E4HR3Fp9gYX+AET4YZmU/VtyLcnRJ9QHdVqHSCzOaElxX30BlyNf5Nw6ZcCafkbB0RGXqSwwsjxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.0-beta.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "30.0.0-beta.3", + "jest-util": "30.0.0-beta.3", + "jest-worker": "30.0.0-beta.3", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/transform/node_modules/jest-regex-util": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.0-beta.3.tgz", + "integrity": "sha512-kiDaZ35ogPivxgLEGJ1jNW2KBtvmPwGlPjy5ASHiVE3kjn3g80galEIcWC0hZV6g5BtTx15VKzSyfOTiKXPnxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/@jest/transform/node_modules/jest-util": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.0-beta.3.tgz", + "integrity": "sha512-kob8YNaO1UPrG0TgGdH5l0ciNGuXDX93Yn2b2VCkALuqOXbqzT2xCr6O7dBuwhM7tmzBbpM6CkcK7Qyf/JmLZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.0-beta.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^4.0.0", + "graceful-fs": "^4.2.9", + "picomatch": "^4.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/@jest/transform/node_modules/jest-worker": { + "version": "30.0.0-beta.3", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.0-beta.3.tgz", + "integrity": "sha512-v17y4Jg9geh3tDm8aU2snuwr8oCJtFefuuPrMRqmC6Ew8K+sLfOcuB3moJ15PHoe4MjTGgsC1oO2PK/GaF1vTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.2.0", + "jest-util": "30.0.0-beta.3", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || >=22.0.0" + } + }, + "node_modules/@jest/transform/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.15.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", + "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-jest/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", + "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001717", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", + "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cloudevents": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/cloudevents/-/cloudevents-8.0.3.tgz", + "integrity": "sha512-wTixKNjfLeyj9HQpESvLVVO4xgdqdvX4dTeg1IZ2SCunu/fxVzCamcIZneEyj31V82YolFCKwVeSkr8zResB0Q==", + "license": "Apache-2.0", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "json-bigint": "^1.0.0", + "process": "^0.11.10", + "util": "^0.12.4", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=16 <=22" + } + }, + "node_modules/cloudevents/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.151", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz", + "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", + "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT" + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-gax": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.0.tgz", + "integrity": "sha512-zKKLeLfcYBVOzzM48Brtn4EQkKcTli9w6c1ilzFK2NbJvcd4ATD8/XqFExImvE/W5IwMlKKwa5qqVufji3ioNQ==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/http-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/http-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "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" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", + "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.1.tgz", + "integrity": "sha512-3qx3IRjR9WPQKagdwrKjO3Gu8RgQR2qqw+1KnigWhoVjFqegIj1K3bP11sGqhxrO46/XL7lekuG4jmjL+4cLsw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-package-up/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT" + }, + "node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/superagent/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/supertest": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.0.tgz", + "integrity": "sha512-5QeSO8hSrKghtcWEoPiO036fxH0Ii2wVQfFZSP0oqQhmjk8bOLhDFXr4JrvaFmPuEWUoq4znY3uSi8UzLKxGqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teeny-request/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/teeny-request/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/teeny-request/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/teeny-request/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..ca8a44a --- /dev/null +++ b/src/package.json @@ -0,0 +1,49 @@ +{ + "name": "tech-report-api", + "version": "1.0.0", + "description": "API for HTTP Archive technology reports", + "main": "index.js", + "type": "module", + "engines": { + "node": ">=22.0.0" + }, + "scripts": { + "start": "export DATABASE=tech-report-api-prod &&node index.js", + "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test:live": "bash ../test-api.sh" + }, + "dependencies": { + "@google-cloud/firestore": "7.3.0", + "@google-cloud/functions-framework": "^4.0.0" + }, + "devDependencies": { + "@jest/transform": "^30.0.0-beta.3", + "jest": "29.7.0", + "nodemon": "3.0.1", + "supertest": "^7.1.0" + }, + "jest": { + "testEnvironment": "node", + "verbose": true, + "collectCoverage": true, + "collectCoverageFrom": [ + "**/*.js", + "!index.js" + ], + "coverageDirectory": "coverage", + "coverageReporters": [ + "text", + "lcov" + ], + "testMatch": [ + "**/__tests__/**/*.js", + "**/?(*.)+(spec|test).js" + ], + "globals": { + "__filename": false, + "__dirname": false + }, + "transformIgnorePatterns": ["node_modules/(?!(.*\\.mjs$))"], + "transform": {} + } +} diff --git a/src/utils/controllerHelpers.js b/src/utils/controllerHelpers.js new file mode 100644 index 0000000..31e1c00 --- /dev/null +++ b/src/utils/controllerHelpers.js @@ -0,0 +1,354 @@ +import { convertToHashes, convertToArray } from './helpers.js'; + +/** + * Common parameter validation patterns + */ +const REQUIRED_PARAMS = { + TECHNOLOGY: 'technology', + GEO: 'geo', + RANK: 'rank', + VERSION: 'version' +}; + +/** + * Validate required parameters for a request + * @param {Object} params - Request query parameters + * @param {Array} required - Array of required parameter names + * @returns {Array|null} - Array of errors or null if valid + */ +const validateRequiredParams = (params, required) => { + const errors = []; + + for (const param of required) { + if (!params[param]) { + errors.push([param, `missing ${param} parameter`]); + } + } + + return errors.length > 0 ? errors : null; +}; + + +/** + * Creates an error response object + * @param {Array>} errors - Array of [key, message] arrays + * @returns {Object} Error response object + */ +const createErrorResponse = (errors) => { + return { + success: false, + errors: convertToHashes(errors) + }; +}; + +/** + * Send error response for missing parameters + * @param {Object} res - Response object + * @param {Array} errors - Array of error tuples + */ +const sendValidationError = (res, errors) => { + res.statusCode = 400; + res.end(JSON.stringify(createErrorResponse(errors))); +}; + +// Cache for latest dates to avoid repeated queries +const latestDateCache = new Map(); +const CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds + +const queryResultCache = new Map(); +const QUERY_CACHE_TTL = 60 * 60 * 1000; // 1 hour in milliseconds + +// Cache size limit +const MAX_CACHE_SIZE = 5000; // Maximum number of cache entries + +/** + * Clean up cache when it exceeds size limit (LRU-style cleanup) + * Removes oldest entries first, including expired ones + */ +const cleanupCacheToSize = () => { + const targetSize = Math.floor(MAX_CACHE_SIZE * 0.5); // Clean to 50% of max size + if (queryResultCache.size <= targetSize) return 0; + + const now = Date.now(); + const entries = Array.from(queryResultCache.entries()); + + // Sort by timestamp (oldest first), prioritizing expired entries + entries.sort((a, b) => { + const aExpired = (now - a[1].timestamp) > QUERY_CACHE_TTL; + const bExpired = (now - b[1].timestamp) > QUERY_CACHE_TTL; + + // If one is expired and the other isn't, prioritize expired for deletion + if (aExpired && !bExpired) return -1; + if (!aExpired && bExpired) return 1; + + // If both have same expiry status, sort by timestamp (oldest first) + return a[1].timestamp - b[1].timestamp; + }); + + const deleteCount = queryResultCache.size - targetSize; + for (let i = 0; i < deleteCount && i < entries.length; i++) { + queryResultCache.delete(entries[i][0]); + } +}; + +/** + * Generate a cache key for a query + * @param {string} collection - Collection name + * @param {Object} filters - Query filters + * @returns {string} - Cache key + */ +const generateQueryCacheKey = (collection, filters) => { + return `${collection}:${JSON.stringify(filters)}`; +}; + +/** + * Get cached query result if available and not expired + * @param {string} cacheKey - Cache key + * @returns {Array|null} - Cached result or null + */ +const getCachedQueryResult = (cacheKey) => { + const cached = queryResultCache.get(cacheKey); + if (cached && (Date.now() - cached.timestamp) < QUERY_CACHE_TTL) { + return cached.data; + } + return null; +}; + +/** + * Cache a query result + * @param {string} cacheKey - Cache key + * @param {Array} data - Query result data + */ +const setCachedQueryResult = (cacheKey, data) => { + // Clean up if cache is getting too large before adding new entry + if (queryResultCache.size >= MAX_CACHE_SIZE) { + cleanupCacheToSize(); + } + + queryResultCache.set(cacheKey, { + data: data, + timestamp: Date.now() + }); +}; + +/** + * Get the latest date from a collection with caching + * @param {Object} firestore - Firestore instance + * @param {string} collection - Collection name + * @returns {string|null} - Latest date or null + */ +const getLatestDate = async (firestore, collection) => { + const now = Date.now(); + const cacheKey = collection; + const cached = latestDateCache.get(cacheKey); + + // Check if we have a valid cached result + if (cached && (now - cached.timestamp) < CACHE_TTL) { + return cached.date; + } + + // Query for latest date + const query = firestore.collection(collection).orderBy('date', 'desc').limit(1); + const snapshot = await query.get(); + + let latestDate = null; + if (!snapshot.empty) { + latestDate = snapshot.docs[0].data().date; + } + + // Cache the result + latestDateCache.set(cacheKey, { + date: latestDate, + timestamp: now + }); + + return latestDate; +}; + +/** + * Apply date filters to a query + * @param {Object} query - Firestore query + * @param {Object} params - Request parameters + * @returns {Object} - Modified query + */ +const applyDateFilters = (query, params) => { + if (params.start) { + query = query.where('date', '>=', params.start); + } + if (params.end) { + query = query.where('date', '<=', params.end); + } + return query; +}; + +/** + * Apply standard filters (geo, rank, technology, version) to a query + * @param {Object} query - Firestore query + * @param {Object} params - Request parameters + * @param {string} technology - Technology name + * @param {Array} techArray - Array of technologies (used for version filtering) + * @returns {Object} - Modified query + */ +const applyStandardFilters = (query, params, technology, techArray = []) => { + if (params.geo) { + query = query.where('geo', '==', params.geo); + } + if (params.rank) { + query = query.where('rank', '==', params.rank); + } + if (technology) { + query = query.where('technology', '==', technology); + } + + // Apply version filter with special handling for 'ALL' case + if (params.version && techArray.length === 1) { + //query = query.where('version', '==', params.version); // TODO: Uncomment when migrating to a new data schema + } else { + //query = query.where('version', '==', 'ALL'); + } + + return query; +}; + +/** + * Process technology array and handle 'latest' date substitution + * @param {Object} firestore - Firestore instance + * @param {Object} params - Request parameters + * @param {string} collection - Collection name + * @returns {Object} - Processed parameters and tech array + */ +const preprocessParams = async (firestore, params, collection) => { + // Handle 'latest' special value for start parameter + if (params.start && params.start === 'latest') { + params.start = await getLatestDate(firestore, collection); + } + + // Handle version 'ALL' special case for multiple technologies + const techArray = convertToArray(params.technology); + if (!params.version || techArray.length > 1) { + params.version = 'ALL'; + } + + return { params, techArray }; +}; + +/** + * Apply array-based filters using 'in' or 'array-contains-any' operators + * @param {Object} query - Firestore query + * @param {string} field - Field name to filter on + * @param {string} value - Comma-separated values or single value + * @param {string} operator - Firestore operator ('in' or 'array-contains-any') + * @returns {Object} - Modified query + */ +const applyArrayFilter = (query, field, value, operator = 'in') => { + if (!value) return query; + const valueArray = convertToArray(value); + + if (valueArray.length > 0) { + query = query.where(field, operator, valueArray); + } + + return query; +}; + +/** + * Select specific fields from an object based on comma-separated field names + * @param {Object} data - Source data object + * @param {string} fieldsParam - Comma-separated field names (e.g., "technology,category") + * @returns {Object} - Object containing only requested fields + */ +const selectFields = (data, fieldsParam) => { + if (!fieldsParam) return data; + + const fields = convertToArray(fieldsParam); + + if (fields.length === 0) return data; + + const result = {}; + fields.forEach(field => { + if (data.hasOwnProperty(field)) { + result[field] = data[field]; + } + }); + + return result; +}; + +/** + * Get cache statistics for monitoring + * @returns {Object} Cache statistics + */ +const getCacheStats = () => { + const now = Date.now(); + + // Count valid vs expired entries + let queryValidCount = 0; + let queryExpiredCount = 0; + for (const [key, value] of queryResultCache) { + if (now - value.timestamp < QUERY_CACHE_TTL) { + queryValidCount++; + } else { + queryExpiredCount++; + } + } + + let dateValidCount = 0; + let dateExpiredCount = 0; + for (const [key, value] of latestDateCache) { + if (now - value.timestamp < CACHE_TTL) { + dateValidCount++; + } else { + dateExpiredCount++; + } + } + + return { + queryCache: { + total: queryResultCache.size, + valid: queryValidCount, + expired: queryExpiredCount, + ttl: QUERY_CACHE_TTL + }, + dateCache: { + total: latestDateCache.size, + valid: dateValidCount, + expired: dateExpiredCount, + ttl: CACHE_TTL + }, + config: { + maxCacheSize: MAX_CACHE_SIZE, + cleanupStrategy: 'size-based-lru' + } + }; +}; + +/** + * Handle controller errors with consistent error response format + * @param {Object} res - Response object + * @param {Error} error - Error object + * @param {string} operation - Description of the operation that failed + */ +const handleControllerError = (res, error, operation) => { + console.error(`Error ${operation}:`, error); + res.statusCode = 500; + res.end(JSON.stringify({ + errors: [{ error: `Failed to ${operation}` }] + })); +}; + +export { + REQUIRED_PARAMS, + validateRequiredParams, + sendValidationError, + getLatestDate, + applyDateFilters, + applyStandardFilters, + preprocessParams, + applyArrayFilter, + selectFields, + handleControllerError, + generateQueryCacheKey, + getCachedQueryResult, + setCachedQueryResult, + getCacheStats +}; diff --git a/src/utils/db.js b/src/utils/db.js new file mode 100644 index 0000000..41eed29 --- /dev/null +++ b/src/utils/db.js @@ -0,0 +1,32 @@ +import { Firestore } from '@google-cloud/firestore'; + +// Initialize Firestore with basic optimizations (default connection using env variables) +const firestore = new Firestore({ + projectId: process.env.PROJECT, + databaseId: process.env.DATABASE, + settings: { + // Enable connection pooling + maxIdleChannels: 10, + // Enable keepalive to reduce connection overhead + keepaliveTime: 30000, + keepaliveTimeout: 5000, + keepalivePermitWithoutCalls: true + } +}); + +// Initialize production Firestore connection with hardcoded database +const firestoreOld = new Firestore({ + projectId: process.env.PROJECT, + databaseId: 'tech-report-apis-prod', + settings: { + // Enable connection pooling + maxIdleChannels: 10, + // Enable keepalive to reduce connection overhead + keepaliveTime: 30000, + keepaliveTimeout: 5000, + keepalivePermitWithoutCalls: true + } +}); + +// Export both connections - maintain backward compatibility +export { firestore, firestoreOld }; diff --git a/src/utils/helpers.js b/src/utils/helpers.js new file mode 100644 index 0000000..9e7ed9c --- /dev/null +++ b/src/utils/helpers.js @@ -0,0 +1,28 @@ +/** + * Utility functions for API requests and responses + */ + +/** + * Converts a comma-separated string to an array + * @param {string} dataString - The string to convert + * @returns {string[]} The resulting array + */ +const convertToArray = (dataString) => { + if (!dataString) return []; + + // URL decode and split by comma + const decoded = decodeURIComponent(dataString); + return decoded.split(','); +}; + +/** + * Converts error arrays to hash format + * @param {Array>} arr - Array of [key, message] arrays + * @returns {Array} Array of {key: message} objects + */ +const convertToHashes = (arr) => { + return arr.map(([key, message]) => ({ [key]: message })); +}; + + +export { convertToArray, convertToHashes }; diff --git a/terraform/dev/main.tf b/terraform/dev/main.tf index 8a8cb78..f73acbb 100644 --- a/terraform/dev/main.tf +++ b/terraform/dev/main.tf @@ -1,10 +1,3 @@ - -provider "google" { - project = "httparchive" - region = "us-east1" - request_timeout = "60m" -} - terraform { backend "gcs" { bucket = "tf-state-backingapi-20230314" @@ -12,43 +5,47 @@ terraform { } } +provider "google" { + project = var.project + region = var.region + request_timeout = "60m" +} + resource "google_api_gateway_api" "api" { provider = google-beta - api_id = "api-gw-dev" - display_name = "The dev API Gateway" - project = "httparchive" + api_id = "reports-api" + display_name = "Reports API Gateway" + project = var.project } -# A Configuration, consisting of an OpenAPI specification resource "google_api_gateway_api_config" "api_config" { provider = google-beta api = google_api_gateway_api.api.api_id - api_config_id_prefix = "api" - project = "httparchive" - display_name = "The dev Config" + api_config_id_prefix = "reports-api-config-dev" + project = var.project + display_name = "Reports API Config DEV" openapi_documents { document { - path = "spec.yaml" + path = "spec.yaml" contents = base64encode(<<-EOF swagger: "2.0" info: - title: reports-backend-api - description: API tech report + title: reports_api_config_dev version: 1.0.0 schemes: - https produces: - application/json +x-google-backend: + address: https://us-central1-httparchive.cloudfunctions.net/tech-report-api-dev + deadline: 60 + path_translation: APPEND_PATH_TO_ADDRESS + protocol: h2 paths: /v1/categories: get: summary: categories operationId: getCategories - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/categories-dev - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -56,11 +53,6 @@ paths: get: summary: adoption operationId: getadoptionReports - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/adoption-dev - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -68,11 +60,6 @@ paths: get: summary: pageWeight operationId: getpageWeight - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/page-weight-dev - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -80,11 +67,6 @@ paths: get: summary: lighthouse operationId: getLighthouseReports - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/lighthouse-dev - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -92,11 +74,6 @@ paths: get: summary: cwv operationId: getCwv - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/cwvtech-dev - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -104,11 +81,6 @@ paths: get: summary: ranks operationId: getRanks - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/ranks-dev - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -116,23 +88,13 @@ paths: get: summary: geos operationId: getGeos - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/geos-dev - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String /v1/technologies: get: - summary: geos + summary: technologies operationId: getTechnologies - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/technologies-dev - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -146,17 +108,17 @@ EOF } } } -# The actual API Gateway + resource "google_api_gateway_gateway" "gateway" { provider = google-beta - project = "httparchive" - region = "us-east1" + project = var.project + region = var.region api_config = google_api_gateway_api_config.api_config.id - gateway_id = "dev-gw" - display_name = "devApi Gateway" + gateway_id = "reports-dev" + display_name = "Reports API Gateway DEV" labels = { owner = "tech_report_api" - environment = "dev" + environment = var.environment } depends_on = [google_api_gateway_api_config.api_config] lifecycle { @@ -166,122 +128,19 @@ resource "google_api_gateway_gateway" "gateway" { } } -module "cwvtech" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "dev" - source_directory = "../../functions/cwvtech" - function_name = "cwvtech" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "lighthouse" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "dev" - source_directory = "../../functions/lighthouse" - function_name = "lighthouse" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "adoption" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "dev" - source_directory = "../../functions/adoption" - function_name = "adoption" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "page-weight" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "dev" - source_directory = "../../functions/page-weight" - function_name = "page-weight" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "categories" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "dev" - source_directory = "../../functions/categories" - function_name = "categories" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "technologies" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "dev" - source_directory = "../../functions/technologies" - function_name = "technologies" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "ranks" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "dev" - source_directory = "../../functions/ranks" - function_name = "ranks" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "geos" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "dev" - source_directory = "../../functions/geos" - function_name = "geos" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway +module "endpoints" { + source = "./../modules/run-service" + entry_point = "app" + project = var.project + environment = var.environment + source_directory = "../../src" + function_name = "tech-report-api" + region = var.region + service_account_email = var.google_service_account_cloud_functions + service_account_api_gateway = var.google_service_account_api_gateway + min_instances = var.min_instances environment_variables = { - "PROJECT" = "httparchive", + "PROJECT" = var.project "DATABASE" = var.project_database } } diff --git a/terraform/dev/variables.tf b/terraform/dev/variables.tf index 7e8a919..4dd84ad 100644 --- a/terraform/dev/variables.tf +++ b/terraform/dev/variables.tf @@ -1,21 +1,34 @@ -variable "google_service_account_cloud_functions" { +variable "project" { + description = "The project name" type = string - description = "Service account for Cloud Functions" + default = "httparchive" } - -variable "google_service_account_api_gateway" { +variable "region" { + type = string + default = "us-central1" +} +variable "environment" { + description = "The environment name" type = string - description = "Service account for API Gateway" + default = "dev" } - variable "project_database" { type = string description = "The database name" + default = "tech-report-api-prod" // TODO: Update this to the DEV database name +} +variable "google_service_account_cloud_functions" { + type = string + description = "Service account for Cloud Functions" +} +variable "google_service_account_api_gateway" { + type = string + description = "Service account for API Gateway" } variable "min_instances" { description = "(Optional) The limit on the minimum number of function instances that may coexist at a given time." type = number - default = 0 + default = 1 // TODO: Update this to 0 } diff --git a/terraform/modules/api-gateway/networking.tf b/terraform/modules/api-gateway/networking.tf index 9fb3462..a07e6e2 100644 --- a/terraform/modules/api-gateway/networking.tf +++ b/terraform/modules/api-gateway/networking.tf @@ -29,7 +29,7 @@ resource "google_compute_managed_ssl_certificate" "default" { managed { domains = ["api.httparchive.org"] } - + } resource "google_compute_target_https_proxy" "default" { @@ -40,7 +40,7 @@ resource "google_compute_target_https_proxy" "default" { project = var.project name = "httparchive-api-gateway-https-proxy" url_map = google_compute_url_map.default[count.index].id - ssl_certificates = [google_compute_managed_ssl_certificate.default[count.index].id] + ssl_certificates = [google_compute_managed_ssl_certificate.default[count.index].id] } resource "google_compute_region_network_endpoint_group" "function_neg" { @@ -57,7 +57,7 @@ resource "google_compute_region_network_endpoint_group" "function_neg" { platform = "apigateway.googleapis.com" resource = google_api_gateway_gateway.gateway.gateway_id } - + } resource "google_compute_backend_service" "backend_neg" { @@ -70,17 +70,17 @@ resource "google_compute_backend_service" "backend_neg" { load_balancing_scheme = "EXTERNAL_MANAGED" protocol = "HTTP" backend { - group = google_compute_region_network_endpoint_group.function_neg[count.index].self_link - } - + group = google_compute_region_network_endpoint_group.function_neg[count.index].self_link + } + } resource "google_compute_url_map" "default" { #count = var.environment == "prod" ? 1 : 0 count = 0 - + provider = google-beta project = var.project name = "httparchive-api-gateway-url-map" default_service = google_compute_backend_service.backend_neg[count.index].self_link -} \ No newline at end of file +} diff --git a/terraform/modules/cloud-function/main.tf b/terraform/modules/run-service/main.tf similarity index 79% rename from terraform/modules/cloud-function/main.tf rename to terraform/modules/run-service/main.tf index f34e080..e2c6760 100644 --- a/terraform/modules/cloud-function/main.tf +++ b/terraform/modules/run-service/main.tf @@ -1,5 +1,5 @@ locals { - bucketName = "tf-cloudfunctions-backingapi-20230314" + bucketName = "tf-cloudfunctions-backingapi-20230314" } data "archive_file" "source" { type = "zip" @@ -13,11 +13,11 @@ resource "google_storage_bucket_object" "zip" { } resource "google_cloudfunctions2_function" "function" { - name = "${var.function_name}-${var.environment}" + name = "${var.function_name}-${var.environment}" location = var.region build_config { - runtime = "python312" + runtime = "nodejs22" entry_point = var.entry_point source { @@ -31,15 +31,16 @@ resource "google_cloudfunctions2_function" "function" { service_config { all_traffic_on_latest_revision = true available_memory = var.available_memory_mb + available_cpu = var.available_cpu ingress_settings = var.ingress_settings environment_variables = var.environment_variables - min_instance_count = var.min_instances - max_instance_count = var.max_instances - timeout_seconds = var.timeout + min_instance_count = var.min_instances + max_instance_count = var.max_instances + timeout_seconds = var.timeout max_instance_request_concurrency = var.max_instance_request_concurrency - service_account_email = var.service_account_email + service_account_email = var.service_account_email } labels = { @@ -72,17 +73,16 @@ resource "google_cloud_run_v2_service_iam_member" "variable_service_account_run_ role = "roles/run.invoker" member = "serviceAccount:${var.service_account_email}" } -// TODO: Conditionally apply if the function needs to be invoked by API Gateway + resource "google_cloudfunctions2_function_iam_member" "api_gw_variable_service_account_function_invoker" { project = google_cloudfunctions2_function.function.project location = google_cloudfunctions2_function.function.location cloud_function = google_cloudfunctions2_function.function.name role = "roles/cloudfunctions.invoker" - #member = "serviceAccount:api-gateway@httparchive.iam.gserviceaccount.com" - member = "serviceAccount:${var.service_account_api_gateway}" - depends_on = [google_cloudfunctions2_function.function] + member = "serviceAccount:${var.service_account_api_gateway}" + depends_on = [google_cloudfunctions2_function.function] } -// TODO: Conditionally apply if the function needs to be invoked by API Gateway + resource "google_cloud_run_v2_service_iam_member" "api_gw_variable_service_account_run_invoker" { project = var.project location = var.region diff --git a/terraform/modules/cloud-function/outputs.tf b/terraform/modules/run-service/outputs.tf similarity index 100% rename from terraform/modules/cloud-function/outputs.tf rename to terraform/modules/run-service/outputs.tf diff --git a/terraform/modules/cloud-function/variables.tf b/terraform/modules/run-service/variables.tf similarity index 91% rename from terraform/modules/cloud-function/variables.tf rename to terraform/modules/run-service/variables.tf index cad4a5c..5ef5596 100644 --- a/terraform/modules/cloud-function/variables.tf +++ b/terraform/modules/run-service/variables.tf @@ -2,8 +2,8 @@ variable "secrets" { default = [] } variable "region" { - default = "us-east1" - type = string + default = "us-central1" + type = string } variable "environment" { description = "The 'Environment' that is being created/deployed. Applied as a suffix to many resources." @@ -22,10 +22,15 @@ variable "entry_point" { type = string } variable "available_memory_mb" { - default = "1Gi" + default = "2Gi" type = string description = "The amount of memory for the Cloud Function" } +variable "available_cpu" { + default = "2" + type = string + description = "The amount of CPU for the Cloud Function" +} variable "ingress_settings" { type = string default = "ALLOW_ALL" @@ -54,19 +59,19 @@ variable "service_account_api_gateway" { description = "API Gateway service account who can invoke this function. This is required!" } variable "max_instances" { - default = 5 + default = 10 type = number description = "(Optional) The limit on the maximum number of function instances that may coexist at a given time." } variable "min_instances" { description = "(Optional) The limit on the minimum number of function instances that may coexist at a given time." type = number - default = 0 + default = 1 } variable "max_instance_request_concurrency" { description = "(Optional) The limit on the maximum number of requests that an instance can handle simultaneously. This can be used to control costs when scaling. Defaults to 1." type = number - default = 5 + default = 18 } variable "environment_variables" { description = "environment_variables" diff --git a/terraform/modules/service-account/main.tf b/terraform/modules/service-account/main.tf index a635292..ab94d00 100644 --- a/terraform/modules/service-account/main.tf +++ b/terraform/modules/service-account/main.tf @@ -6,9 +6,9 @@ resource "google_service_account" "service_account" { display_name = var.display_name } resource "google_project_iam_member" "permissions" { - for_each = toset(var.permissions) - project = var.project - role = each.key - member = google_service_account.service_account.member + for_each = toset(var.permissions) + project = var.project + role = each.key + member = google_service_account.service_account.member depends_on = [google_service_account.service_account] } diff --git a/terraform/modules/service-account/outputs.tf b/terraform/modules/service-account/outputs.tf index 970c423..d5764fd 100644 --- a/terraform/modules/service-account/outputs.tf +++ b/terraform/modules/service-account/outputs.tf @@ -5,4 +5,4 @@ output "email" { output "member" { description = "The Identity of the service account in the form serviceAccount:{email}. This value is often used to refer to the service account in order to grant IAM permissions." value = google_service_account.service_account.member -} \ No newline at end of file +} diff --git a/terraform/modules/service-account/variables.tf b/terraform/modules/service-account/variables.tf index b0f5e44..c3ab94b 100644 --- a/terraform/modules/service-account/variables.tf +++ b/terraform/modules/service-account/variables.tf @@ -14,4 +14,4 @@ variable "permissions" { default = [] type = list(string) description = "A list of IAM Permissions for the Service Account" -} \ No newline at end of file +} diff --git a/terraform/prod/main.tf b/terraform/prod/main.tf index f0cc004..5a08aba 100644 --- a/terraform/prod/main.tf +++ b/terraform/prod/main.tf @@ -1,10 +1,3 @@ - -provider "google" { - project = "httparchive" - region = "us-east1" - request_timeout = "60m" -} - terraform { backend "gcs" { bucket = "tf-state-backingapi-20230314" @@ -12,42 +5,47 @@ terraform { } } +provider "google" { + project = var.project + region = var.region + request_timeout = "60m" +} + resource "google_api_gateway_api" "api" { provider = google-beta - api_id = "api-gw-prod" - display_name = "The prod API Gateway" - project = "httparchive" + api_id = "reports-api-prod" + display_name = "Reports API Gateway" + project = var.project } resource "google_api_gateway_api_config" "api_config" { provider = google-beta api = google_api_gateway_api.api.api_id - api_config_id_prefix = "api" - project = "httparchive" - display_name = "The prod Config" + api_config_id_prefix = "reports-api-config-prod" + project = var.project + display_name = "Reports API Config PROD" openapi_documents { document { - path = "spec.yaml" + path = "spec.yaml" contents = base64encode(<<-EOF swagger: "2.0" info: - title: reports-backend-api - description: API tech report + title: reports_api_config_prod version: 1.0.0 schemes: - https produces: - application/json +x-google-backend: + address: https://us-central1-httparchive.cloudfunctions.net/tech-report-api-prod + deadline: 60 + path_translation: APPEND_PATH_TO_ADDRESS + protocol: h2 paths: /v1/categories: get: summary: categories operationId: getCategories - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/categories-prod - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -55,11 +53,6 @@ paths: get: summary: adoption operationId: getadoptionReports - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/adoption-prod - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -67,11 +60,6 @@ paths: get: summary: pageWeight operationId: getpageWeight - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/page-weight-prod - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -79,11 +67,6 @@ paths: get: summary: lighthouse operationId: getLighthouseReports - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/lighthouse-prod - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -91,11 +74,6 @@ paths: get: summary: cwv operationId: getCwv - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/cwvtech-prod - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -103,11 +81,6 @@ paths: get: summary: ranks operationId: getRanks - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/ranks-prod - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -115,23 +88,13 @@ paths: get: summary: geos operationId: getGeos - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/geos-prod - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String /v1/technologies: get: - summary: geos + summary: technologies operationId: getTechnologies - x-google-backend: - address: https://us-east1-httparchive.cloudfunctions.net/technologies-prod - deadline: 60 - # security: - # - api_key: [] responses: 200: description: String @@ -148,14 +111,14 @@ EOF resource "google_api_gateway_gateway" "gateway" { provider = google-beta - project = "httparchive" - region = "us-east1" + project = var.project + region = var.region api_config = google_api_gateway_api_config.api_config.id - gateway_id = "prod-gw" - display_name = "prod Api Gateway" + gateway_id = "reports-prod" + display_name = "Reports API Gateway PROD" labels = { owner = "tech_report_api" - environment = "prod" + environment = var.environment } depends_on = [google_api_gateway_api_config.api_config] lifecycle { @@ -165,123 +128,19 @@ resource "google_api_gateway_gateway" "gateway" { } } -module "cwvtech" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "prod" - source_directory = "../../functions/cwvtech" - function_name = "cwvtech" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "lighthouse" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "prod" - source_directory = "../../functions/lighthouse" - function_name = "lighthouse" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "adoption" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "prod" - source_directory = "../../functions/adoption" - function_name = "adoption" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "page-weight" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "prod" - source_directory = "../../functions/page-weight" - function_name = "page-weight" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "categories" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "prod" - source_directory = "../../functions/categories" - function_name = "categories" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "technologies" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "prod" - source_directory = "../../functions/technologies" - function_name = "technologies" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - min_instances = var.min_instances - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "ranks" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "prod" - source_directory = "../../functions/ranks" - function_name = "ranks" - service_account_email = var.google_service_account_cloud_functions - service_account_api_gateway = var.google_service_account_api_gateway - environment_variables = { - "PROJECT" = "httparchive", - "DATABASE" = var.project_database - } -} - -module "geos" { - source = "./../modules/cloud-function" - entry_point = "dispatcher" - project = "httparchive" - environment = "prod" - source_directory = "../../functions/geos" - function_name = "geos" - service_account_email = var.google_service_account_cloud_functions +module "endpoints" { + source = "./../modules/run-service" + entry_point = "app" + project = var.project + environment = var.environment + source_directory = "../../src" + function_name = "tech-report-api" + region = var.region + service_account_email = var.google_service_account_cloud_functions service_account_api_gateway = var.google_service_account_api_gateway + min_instances = var.min_instances environment_variables = { - "PROJECT" = "httparchive", + "PROJECT" = var.project "DATABASE" = var.project_database } } diff --git a/terraform/prod/variables.tf b/terraform/prod/variables.tf index 05d2b80..edec25f 100644 --- a/terraform/prod/variables.tf +++ b/terraform/prod/variables.tf @@ -1,19 +1,31 @@ -variable "google_service_account_cloud_functions" { +variable "project" { + description = "The project name" type = string - description = "Service account for Cloud Functions" + default = "httparchive" } - -variable "google_service_account_api_gateway" { +variable "region" { + default = "us-central1" + type = string +} +variable "environment" { + description = "The environment name" type = string - description = "Service account for API Gateway" + default = "prod" } - variable "project_database" { type = string description = "The database name" - + default = "tech-report-api-prod" } +variable "google_service_account_cloud_functions" { + type = string + description = "Service account for Cloud Functions" +} +variable "google_service_account_api_gateway" { + type = string + description = "Service account for API Gateway" +} variable "min_instances" { description = "(Optional) The limit on the minimum number of function instances that may coexist at a given time." type = number diff --git a/test-api.sh b/test-api.sh new file mode 100755 index 0000000..ea98db3 --- /dev/null +++ b/test-api.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +# Function to test an endpoint +test_endpoint() { + local endpoint=$1 + local params=$2 + local url="http://localhost:3000${endpoint}${params}" + + echo "Testing endpoint: ${url}" + response=$(curl -s -w "\n%{http_code}" "${url}") + http_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + echo "$body" | jq . | head -10 + echo "Status code: $http_code" + + if [[ $http_code -ne 200 ]]; then + echo "Error: Endpoint returned non-200 status code" + exit 1 + fi + + echo "" + echo "----------------------" + echo "" +} + +# Function to test CORS preflight with OPTIONS request +test_cors_preflight() { + local endpoint=$1 + local url="http://localhost:3000${endpoint}" + + echo "Testing CORS preflight for: ${url}" + + # Send OPTIONS request with CORS headers + response=$(curl -s -X OPTIONS -w "\n%{http_code}" \ + -H "Origin: http://example.com" \ + -H "Access-Control-Request-Method: GET" \ + -H "Access-Control-Request-Headers: Content-Type" \ + "${url}") + + http_code=$(echo "$response" | tail -n1) + headers=$(curl -s -X OPTIONS -I \ + -H "Origin: http://example.com" \ + -H "Access-Control-Request-Method: GET" \ + -H "Access-Control-Request-Headers: Content-Type" \ + "${url}") + + # Check for CORS headers + echo "CORS Headers:" + echo "$headers" | grep -i "access-control" + + echo "Status code: $http_code" + + # OPTIONS preflight should return 204 (No Content) or 200 + if [[ $http_code -ne 204 && $http_code -ne 200 ]]; then + echo "Error: CORS preflight failed with non-200/204 status code" + exit 1 + fi + + # Check for required CORS headers + if ! echo "$headers" | grep -q "Access-Control-Allow-Origin"; then + echo "Error: Missing Access-Control-Allow-Origin header" + exit 1 + fi + + if ! echo "$headers" | grep -q "Access-Control-Allow-Methods"; then + echo "Error: Missing Access-Control-Allow-Methods header" + exit 1 + fi + + echo "CORS preflight check passed!" + echo "" + echo "----------------------" + echo "" +} + +# Start tests +echo "Testing API endpoints..." +echo "----------------------" +echo "" + +# Test health check +test_cors_preflight "/" +test_endpoint "/" "" + +# Test technologies endpoint +test_cors_preflight "/v1/technologies" +test_endpoint "/v1/technologies" "?technology=WordPress&onlyname=true" +test_endpoint "/v1/technologies" "?technology=WordPress&onlyname=true&fields=technology,icon" +test_endpoint "/v1/technologies" "?technology=WordPress&category=CMS&fields=technology,icon" + +# Test categories endpoint +test_cors_preflight "/v1/categories" +test_endpoint "/v1/categories" "?category=CMS&onlyname=true" +test_endpoint "/v1/categories" "?category=CMS&fields=category" + +# Test ranks endpoint +test_endpoint "/v1/ranks" "" + +# Test geos endpoint +test_endpoint "/v1/geos" "" + +# Test adoption endpoint +test_endpoint "/v1/adoption" "?technology=WordPress&geo=ALL&rank=ALL&start=latest" + +# Test cwv endpoint +test_endpoint "/v1/cwv" "?technology=WordPress,Drupal&geo=ALL&rank=ALL&start=latest" + +# Test lighthouse endpoint +test_endpoint "/v1/lighthouse" "?technology=WordPress&geo=ALL&rank=ALL&start=latest" + +# Test page-weight endpoint +test_endpoint "/v1/page-weight" "?technology=WordPress&geo=ALL&rank=ALL&start=latest" + +echo "API tests complete! All endpoints returned 200 status code and CORS is properly configured." diff --git a/tests/test_geos/libs/test_geos_result.py b/tests/test_geos/libs/test_geos_result.py deleted file mode 100644 index 5b306cb..0000000 --- a/tests/test_geos/libs/test_geos_result.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest -from functions.geos.libs.result import Result - -def test_success(): - r = Result(status="success") - assert r.success() == True - assert r.failure() == False - -def test_failure(): - r = Result(errors=["some error"]) - assert r.success() == False - assert r.failure() == True - -def test_default_status(): - r = Result() - assert r.status == "ok" - assert r.success() == True - assert r.failure() == False - -def test_custom_status(): - r = Result(status="custom") - assert r.status == "custom" - assert r.success() == True - assert r.failure() == False - -def test_result(): - r = Result(result="some result") - assert r.result == "some result" - -def test_errors(): - r = Result(errors=["some error"]) - assert r.errors == ["some error"] diff --git a/tests/test_geos/libs/test_geos_utils.py b/tests/test_geos/libs/test_geos_utils.py deleted file mode 100644 index e3b55d7..0000000 --- a/tests/test_geos/libs/test_geos_utils.py +++ /dev/null @@ -1,8 +0,0 @@ -from functions.geos.libs.utils import * -from functions.geos.libs.result import Result -import json - -def test_convert_to_hashes(): - input_arr = [["geo", "missing geo parameters"], ["app", "missing geo parameters"]] - expected_output_arr = [{'geo': 'missing geo parameters'}, {'app': 'missing geo parameters'}] - assert convert_to_hashes(input_arr) == expected_output_arr diff --git a/tests/test_geos/test_geos_main.py b/tests/test_geos/test_geos_main.py deleted file mode 100644 index f346370..0000000 --- a/tests/test_geos/test_geos_main.py +++ /dev/null @@ -1,20 +0,0 @@ -import unittest -import json -from unittest.mock import Mock - -from functions.geos.main import dispatcher -from functions.geos.libs.utils import COUNTRIES - -class TestCloudFunction(unittest.TestCase): - - def test_success(self): - request = Mock() - response = dispatcher(request) - expected_data = json.dumps(COUNTRIES) - - self.assertEqual(response[1], 200) - self.assertEqual(response[0], expected_data) - -if __name__ == '__main__': - unittest.main() - diff --git a/tests/test_ranks/libs/test_ranks_result.py b/tests/test_ranks/libs/test_ranks_result.py deleted file mode 100644 index 7118f70..0000000 --- a/tests/test_ranks/libs/test_ranks_result.py +++ /dev/null @@ -1,31 +0,0 @@ -from functions.ranks.libs.result import Result - -def test_success(): - r = Result(status="success") - assert r.success() == True - assert r.failure() == False - -def test_failure(): - r = Result(errors=["some error"]) - assert r.success() == False - assert r.failure() == True - -def test_default_status(): - r = Result() - assert r.status == "ok" - assert r.success() == True - assert r.failure() == False - -def test_custom_status(): - r = Result(status="custom") - assert r.status == "custom" - assert r.success() == True - assert r.failure() == False - -def test_result(): - r = Result(result="some result") - assert r.result == "some result" - -def test_errors(): - r = Result(errors=["some error"]) - assert r.errors == ["some error"] diff --git a/tests/test_ranks/libs/test_ranks_utils.py b/tests/test_ranks/libs/test_ranks_utils.py deleted file mode 100644 index cfe142a..0000000 --- a/tests/test_ranks/libs/test_ranks_utils.py +++ /dev/null @@ -1,8 +0,0 @@ -from functions.ranks.libs.utils import * -from functions.ranks.libs.result import Result -import json - -def test_convert_to_hashes(): - input_arr = [["geo", "missing geo parameters"], ["app", "missing geo parameters"]] - expected_output_arr = [{'geo': 'missing geo parameters'}, {'app': 'missing geo parameters'}] - assert convert_to_hashes(input_arr) == expected_output_arr diff --git a/tests/test_ranks/test_ranks_main.py b/tests/test_ranks/test_ranks_main.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_technologies/libs/test_technologies_result.py b/tests/test_technologies/libs/test_technologies_result.py deleted file mode 100644 index 29f9695..0000000 --- a/tests/test_technologies/libs/test_technologies_result.py +++ /dev/null @@ -1,32 +0,0 @@ -import pytest -from functions.technologies.libs.result import Result - -def test_success(): - r = Result(status="success") - assert r.success() == True - assert r.failure() == False - -def test_failure(): - r = Result(errors=["some error"]) - assert r.success() == False - assert r.failure() == True - -def test_default_status(): - r = Result() - assert r.status == "ok" - assert r.success() == True - assert r.failure() == False - -def test_custom_status(): - r = Result(status="custom") - assert r.status == "custom" - assert r.success() == True - assert r.failure() == False - -def test_result(): - r = Result(result="some result") - assert r.result == "some result" - -def test_errors(): - r = Result(errors=["some error"]) - assert r.errors == ["some error"] diff --git a/tests/test_technologies/libs/test_technologies_utils.py b/tests/test_technologies/libs/test_technologies_utils.py deleted file mode 100644 index 797ba38..0000000 --- a/tests/test_technologies/libs/test_technologies_utils.py +++ /dev/null @@ -1,8 +0,0 @@ -from functions.technologies.libs.utils import * -from functions.technologies.libs.result import Result -import json - -def test_convert_to_hashes(): - input_arr = [["geo", "missing geo parameters"], ["app", "missing geo parameters"]] - expected_output_arr = [{'geo': 'missing geo parameters'}, {'app': 'missing geo parameters'}] - assert convert_to_hashes(input_arr) == expected_output_arr diff --git a/tests/test_technologies/test_technologies_main.py b/tests/test_technologies/test_technologies_main.py deleted file mode 100644 index e69de29..0000000