diff --git a/.env.example b/.env.example index 4cc1db0..a90f4b1 100644 --- a/.env.example +++ b/.env.example @@ -7,4 +7,14 @@ SearchPostFilterCacheTtlMs=60000 SearchPostFilterCacheMaxEntries=100 SearchPostFilterColdSearchConcurrency=10 SearchPostFilterColdSearchMaxQueueDepth=500 +SearchPostFilterColdSearchMaxWaitMs=10000 +SearchOpenSearchRequestTimeoutMs=20000 +SearchRequestDeadlineMs=30000 +SearchReadinessTimeoutMs=5000 +RequestLogSuccessSampleRate=0.1 +RequestLogHealthChecks=false +WebSearchEnabled=true +SearchRateLimitEnabled=false +SearchRateLimitWindowMs=60000 +SearchRateLimitMaxRequests=0 NPM_CA_CERT= diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..95eff65 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,33 @@ +name: Search API Quality + +on: + pull_request: + +permissions: + contents: read + +concurrency: + group: search-api-quality-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + quality: + name: Search API Quality + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24.14.1 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run quality checks + run: npm run quality diff --git a/.gitignore b/.gitignore index e3a9605..5e19885 100644 --- a/.gitignore +++ b/.gitignore @@ -81,9 +81,6 @@ web_modules/ .env.test.local .env.production.local .env.local -test/artillery/.env -test/artillery/.env.* -!test/artillery/.env.example # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/README.md b/README.md index 9ea51b4..6201913 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,16 @@ Rename `.env.example` to `.env` and replace or check the following values. - `SearchPostFilterCacheMaxEntries` - optional per-process CPE post-filter cache entry limit, default `100` - `SearchPostFilterColdSearchConcurrency` - optional per-process concurrent cold CPE candidate search limit, default `10` - `SearchPostFilterColdSearchMaxQueueDepth` - optional per-process waiting queue depth for cold CPE candidate searches, default `500` +- `SearchPostFilterColdSearchMaxWaitMs` - optional maximum cold CPE candidate queue wait, default `10000` +- `SearchOpenSearchRequestTimeoutMs` - optional timeout for each OpenSearch request, default `20000` +- `SearchRequestDeadlineMs` - optional overall `/search` processing deadline, default `30000` +- `SearchReadinessTimeoutMs` - optional timeout for the OpenSearch index readiness check, default `5000` +- `RequestLogSuccessSampleRate` - optional fraction of successful non-health-check access logs to retain, default `0.1` +- `RequestLogHealthChecks` - optional flag to retain successful health-check access logs, default `false` +- `WebSearchEnabled` - optional deprecated `/webSearch` feature flag, default `true` +- `SearchRateLimitEnabled` - optional per-process `/search` rate-limit flag, default `false` +- `SearchRateLimitWindowMs` - optional rate-limit window in milliseconds, default `60000` +- `SearchRateLimitMaxRequests` - required positive per-process request limit when rate limiting is enabled - `NPM_CA_CERT` - optional absolute host path to a corporate or self-signed CA certificate bundle used only during local Docker Compose builds ```bash @@ -75,9 +85,31 @@ npm start The Search API will be served at `localhost:3000`. +## Health Checks + +`GET /api/health-check` is a process liveness check. It returns HTTP 200 when the Node.js process is running and deliberately does not contact OpenSearch. Use it where a dependency outage should not restart otherwise healthy API tasks. + +`GET /api/ready-check` sends a minimal zero-result search to the configured CVE index. This verifies connectivity, index availability, and the same search permission used by the API without retrieving CVE records or counting all matches. It returns HTTP 200 when search requests can be served. Configuration errors, connection failures, timeouts, incompatible `cve-core` SearchReader contracts, and a missing index return a sanitized HTTP 503 `SEARCH_NOT_READY` response with `Retry-After: 5`. The OpenSearch request is limited to five seconds by default and can be changed with `SearchReadinessTimeoutMs`. + +An AWS load balancer can use `/api/ready-check` to stop routing search traffic to an unready task while the container orchestrator continues using `/api/health-check` for process liveness. Coordinate that probe selection with the infrastructure team before changing deployed health-check behavior. + ## API Input Validation -`POST /search` validates supported JSON request body fields, boolean parameters, pagination values, CVSS severity values, required parameter combinations, CPE Name requirements, and date-time formats. +`POST /search` accepts an optional JSON object request body and validates supported fields, boolean parameters, pagination values, CVSS severity values, required parameter combinations, CPE Name requirements, and date-time formats. A non-empty body must use the `application/json` media type with UTF-8 encoding; unsupported media types or encodings return HTTP 415, and malformed JSON returns HTTP 400. If OpenSearch is unavailable, the endpoint returns HTTP 503 with a `Retry-After` header. + +String inputs are bounded before query construction: + +| Field | Limit | +| ---------------------------- | ---------------------------------------------------------------------: | +| `keywordSearch` | 512 characters and 64 whitespace-delimited terms | +| `cveIds` | 4,096 characters overall, 100 IDs, and 64 characters per ID | +| `cweId` | 32 characters | +| `cpeName` | 2,048 characters | +| `virtualMatchString` | 2,048 characters | +| `versionStart`, `versionEnd` | 256 characters each | +| CVSS metric fields | 256 characters, 32 slash-delimited tokens, and 32 characters per token | + +Requests that exceed a limit return HTTP 400 before contacting OpenSearch. These bounds limit input size and query complexity without changing the documented CPE matching semantics. Use `cveIds` to request up to 100 CVE records by comma-separated CVE ID. @@ -117,11 +149,15 @@ curl -X POST "http://localhost:3000/search" \ -d '{"cpeName":"cpe:2.3:a:freetype:freetype:*:*:*:*:*:*:*:*"}' ``` -The valid query can match stored CPE Match Criteria that use either a concrete version or a wildcard version plus range fields such as `versionStartIncluding`, `versionStartExcluding`, `versionEndIncluding`, or `versionEndExcluding`. Use `virtualMatchString` instead of `cpeName` when searching for partial CPE Match String values such as all versions of a product. +The valid query can match stored CPE Match Criteria that use either a concrete version or a wildcard version plus range fields such as `versionStartIncluding`, `versionStartExcluding`, `versionEndIncluding`, or `versionEndExcluding`. In accordance with the CPE 2.3 Name Matching specification, CPE string literal comparisons are insensitive to lexical case, so an input component such as `macos` matches stored criteria containing `macOS`. Every concrete non-version component in the input, such as edition, language, target software, or target hardware, must be compatible with the same CPE Match Criteria entry that satisfies the version and vulnerability checks. Optional input components set to `*` remain broad matches. Use `virtualMatchString` instead of `cpeName` when searching for partial CPE Match String values such as all versions of a product. + +The version component `-` is the CPE logical value `NA`, meaning that no meaningful version applies. Numeric version range boundaries on wildcard-version criteria do not exclude an asset whose version is `-`; an exact concrete criteria version still does not match `-`, and an exclusive `-` boundary still excludes it. This rule can produce different range behavior than a literal reading of source CVE Record applicability fields. + +Set `isVulnerable` to `true` with `cpeName` to require the matching CPE Match Criteria entry itself to be marked vulnerable. A vulnerable flag on an unrelated CPE entry does not satisfy the request. Omitting `isVulnerable`, or setting it to `false`, does not apply a vulnerability restriction. ### Virtual Match String Search -Use `virtualMatchString` when a user needs NVD-style CPE Match Criteria compatibility matching instead of concrete asset matching. Omitted trailing components and `*` components act as broad matches, matching NVD's broad CPE Match String behavior. +Use `virtualMatchString` when a user needs CPE Match Criteria compatibility matching instead of concrete asset matching. String literal components are compared without regard to lexical case. Omitted trailing components and `*` components act as broad matches. Partial product example: @@ -141,7 +177,7 @@ curl -X POST "http://localhost:3000/search" \ ### Virtual Match String Version Ranges -Use `versionStart`, `versionStartType`, `versionEnd`, and `versionEndType` with `virtualMatchString` to search CPE Match String ranges. Each provided version bound must include its matching type, and the `virtualMatchString` must be a partial CPE 2.3 match string without a version component. +Use `versionStart`, `versionStartType`, `versionEnd`, and `versionEndType` with `virtualMatchString` to search CPE Match String ranges. Each provided version bound must include its matching type, and the `virtualMatchString` must be a partial CPE 2.3 match string without a version component. When both bounds are provided, `versionStart` must be less than `versionEnd`; equal values are valid only when both bounds are `including`. ```bash curl -X POST "http://localhost:3000/search" \ @@ -151,9 +187,15 @@ curl -X POST "http://localhost:3000/search" \ Version ranges use overlap semantics. A stored wildcard-version CPE Match Criteria entry such as `cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*` with `versionEndExcluding=6.9` matches requested ranges that overlap versions before `6.9`, and does not match a requested range that starts at `6.9`. -Future recommendation: broad `virtualMatchString` values can be expensive because they may require large candidate scans plus semantic CPE post-filtering. Requiring concrete leading components, such as `part` and `vendor` for normal searches and `part`, `vendor`, and `product` for version range searches, would reduce the most expensive public search shapes. That would intentionally diverge from NVD behavior and should be handled as a policy decision before implementation. +CPE version comparisons used by both `cpeName` applicability and `virtualMatchString` version ranges use Maven-style qualifier precedence: `alpha`/`a` < `beta`/`b` < `milestone`/`m` < `rc`/`cr` < `snapshot` < an unqualified release/`ga`/`final`/`release` < `sp` < unknown qualifiers. Unknown qualifiers, including `hf`, sort after known qualifiers in case-insensitive lexical order. This means a range beginning at `1.0.0-rc1` includes `1.0.0`, while a range ending before `1.0.0` can include prerelease versions. -`POST /webSearch` requires a non-empty JSON `query` string. It also accepts optional `sort`, `from`, and `size` fields: +Numeric segments used in version range comparisons ignore leading zeros. A stored exclusive upper bound of `10.05.1` therefore includes `10.5.0`, and CVE-2026-33280's exclusive `6.02` upper bound includes `6.1` but excludes `6.2`. This normalization applies only to range comparison. Exact CPE Match Criteria preserve leading zeros, so the CVE-2025-9392 versions `1.0.04.001` and `1.0.4.1` are not equivalent. This comparison rule is not a universal firmware-versioning rule and can differ from a vendor's actual release sequence. + +Future recommendation: broad `virtualMatchString` values can be expensive because they may require large candidate scans plus semantic CPE post-filtering. Requiring concrete leading components, such as `part` and `vendor` for normal searches and `part`, `vendor`, and `product` for version range searches, would reduce the most expensive public search shapes. Narrowing the accepted searches should be handled as a policy decision before implementation. + +`POST /webSearch` is deprecated in favor of `POST /search`. It remains enabled by default during consumer migration and can be disabled without a code change by setting `WebSearchEnabled=false` and restarting the service. When disabled, it returns HTTP `410` with the stable error code `WEB_SEARCH_DISABLED` before validating the request or contacting OpenSearch. + +While enabled, `POST /webSearch` requires a non-empty JSON `query` string. It also accepts optional `sort`, `from`, and `size` fields: ```json { @@ -199,18 +241,25 @@ npm run coverage npm run quality ``` -Artillery live API checks can be run against a running API and OpenSearch deployment: +### Operational Logging -```bash -npm run artillery:smoke -npm run artillery:contract -npm run artillery:load -npm run artillery:stress -``` +Every request receives an `X-Request-ID` response header. A caller-provided `X-Request-ID` is preserved when it contains only letters, numbers, `.`, `_`, `:`, or `-` and is no longer than 128 characters; otherwise the API generates a UUID. The same value appears as `request-id` in access logs and `requestId` in structured `/search` events. + +Access logs omit query strings and request bodies. By default, the API retains 10% of successful non-health-check access logs, suppresses successful `/api/health-check` and `/api/ready-check` access logs, and retains every HTTP 4xx or 5xx access log. Set `RequestLogSuccessSampleRate` from `0` through `1` to change successful-request sampling. Set `RequestLogHealthChecks=true` to retain successful health-check logs. + +The API always emits a sanitized JSON `search.performance` event for `/search` queue saturation, queue wait timeout, search timeout, client disconnect, OpenSearch connection errors, and upstream search errors. These events include the request ID, outcome, HTTP status, strategy, cache state, total elapsed time, cancellation reason, and queue state when applicable. Search terms, CPE values, request bodies, credentials, and upstream error details are never included. + +Set `SearchLogPerformanceTimings=true` on the API deployment when diagnosing slow `/search` requests. Successful requests then emit the same structured event with additional redacted timing details, including candidate counts, filtered counts, returned count, OpenSearch operation types, maximum observed active OpenSearch requests in the process, OpenSearch elapsed time, OpenSearch `took`, post-filter elapsed time, cold-search queue wait time, page fetch elapsed or wait time, and total handler elapsed time. Leave it disabled during normal operation unless the deployment logging volume is acceptable. + +Each `/search` request owns a lifecycle cancellation signal. When its overall deadline expires, the API aborts the active OpenSearch transport request before returning HTTP 504. When the caller disconnects, the API aborts work that is needed only by that caller and does not attempt to write a response. This prevents timed-out or abandoned standard searches from continuing until the transport timeout. -See `test/artillery/README.md` for environment selection, Artillery Cloud recording, and target override examples. +### Application Rate Limiting -Set `SearchLogPerformanceTimings=true` on the API deployment when diagnosing slow `/search` requests. The timing log is redacted and includes request strategy, cache hit status, in-flight hit status, candidate counts, filtered counts, returned count, OpenSearch elapsed time, OpenSearch `took`, post-filter elapsed time, cold-search queue wait time, page fetch elapsed or wait time, and total handler elapsed time. Leave it disabled during normal operation unless the deployment logging volume is acceptable. +Application rate limiting is disabled by default and applies only to `POST /search`. It does not apply to `/webSearch`, health checks, Swagger, OpenAPI, or static assets. + +The fallback limiter uses one in-memory counter per Node.js process. Set `SearchRateLimitEnabled=true`, provide a positive `SearchRateLimitMaxRequests`, and optionally change `SearchRateLimitWindowMs` to enable it. Invalid enabled configuration prevents the application from starting. Rejected requests return HTTP 429 with `SEARCH_RATE_LIMITED`, `Retry-After`, `RateLimit`, and `RateLimit-Policy` headers, and emit a sanitized `search.performance` event with the `rate_limited` outcome. + +This is a process-wide safety limit, not a precise distributed quota. Each ECS task has an independent counter, so total permitted traffic scales with the number of tasks and load-balancer routing. It deliberately does not use caller IP addresses or forwarded headers until the ALB proxy chain and Express `trust proxy` setting are confirmed. AWS WAF or another infrastructure-level distributed limit remains the preferred public-facing control. ### CPE Post-Filter Cache @@ -224,13 +273,18 @@ The cache is intentionally small and short-lived: - Max entries: 100 - Scope: one Node.js process - Stored value: filtered CVE IDs and candidate count -- In-flight behavior: concurrent identical cold searches share one candidate query per process +- In-flight behavior: concurrent identical cold searches share one reference-counted candidate query per process - Cold-search concurrency: cold CPE candidate queries are limited to 10 per process before reaching OpenSearch - Cold-search queue depth: up to 500 unique cold searches can wait per process behind the concurrency limit +- Cold-search queue wait: up to 10 seconds before a waiting search returns HTTP 429 +- OpenSearch request timeout: 20 seconds for each operation +- Overall `/search` deadline: 30 seconds - Eviction: oldest entries are removed when the cache exceeds its max size When the cold-search queue is full, new unique CPE post-filter searches fail closed with HTTP 429 and a `SEARCH_QUEUE_FULL` response. Identical requests for a search that is already in flight still wait on the shared in-flight result and do not consume additional queue slots. +When a cold search waits in the queue for more than 10 seconds, it fails closed with HTTP 429 and a `SEARCH_QUEUE_TIMEOUT` response. OpenSearch operations use one attempt and return a sanitized HTTP 504 `SEARCH_TIMEOUT` response when they exceed 20 seconds. The same response is returned when a `/search` request exceeds the 30-second overall processing deadline, and the active OpenSearch request is aborted. + Healthy dev timing logs showed the impact for repeated identical `virtualMatchString` range searches: | Metric | Cold Cache | Warm Cache | @@ -244,38 +298,9 @@ Healthy dev timing logs showed the impact for repeated identical `virtualMatchSt | Avg candidates scanned | 10000 | cached from prior result | | Avg filtered CVEs | 4 | 4 | -This helps most when multiple users submit the same expensive CPE search within the TTL. The first request is a cold cache miss; later identical requests on the same running task are warm cache hits. If identical cold requests arrive concurrently before the first one finishes, the later requests wait on the same candidate query instead of starting duplicate 10,000-candidate OpenSearch requests. Each request still fetches its requested page of full CVE records after the shared candidate result is available. - -The cache does not share entries across ECS tasks. With multiple tasks, each task warms independently depending on load balancer routing. Steady repeated traffic can warm every active task, but unique one-off searches still pay the cold-cache cost. The per-process TTL, entry limit, cold-search concurrency, and cold-search queue depth can be tuned with `SearchPostFilterCacheTtlMs`, `SearchPostFilterCacheMaxEntries`, `SearchPostFilterColdSearchConcurrency`, and `SearchPostFilterColdSearchMaxQueueDepth`. Future scale improvements could add a shared cache such as Redis/ElastiCache. - -### Interpreting Artillery Results - -Artillery exits with status code `0` when the profile finishes and all expectations and SLO checks pass. A non-zero exit means at least one request expectation, virtual user run, or `ensure` SLO check failed. - -Expectation failures come from request-level `expect` checks. These validate HTTP response behavior such as status codes, JSON content type, and required response properties. A failed expectation usually points to a route contract issue, an unavailable upstream dependency, or a test payload that no longer matches the API contract. - -SLO failures come from the `ensure` gates in each profile: - -- `smoke.yml`: p95 response time must stay under 5000 ms and virtual user error rate must be 0. -- `contract.yml`: p95 response time must stay under 5000 ms and virtual user error rate must be 0. -- `load.yml`: p95 response time must stay under 3000 ms, p99 response time must stay under 8000 ms, and virtual user error rate must be at or below 1%. -- `stress.yml`: p95 response time must stay under 8000 ms, p99 response time must stay under 15000 ms, and virtual user error rate must be at or below 5%. - -Latency percentiles summarize response time distribution. `p95` means 95% of measured responses completed at or below that value; `p99` means 99% completed at or below that value. A high p95 or p99 with successful status codes usually means the API is reachable but slower than the profile allows. - -Virtual user counters show whether Artillery completed the test flow. `vusers.created` is the number of virtual users started, and `vusers.completed` is the number that finished. A gap between those values contributes to the virtual user error rate used by `maxErrorRate`. - -Endpoint-level metrics are grouped by stable request names such as `POST /search keyword`, `POST /search cveIds`, and `POST /webSearch keyword`. Use these groups to identify whether a failure or latency spike is broad across the API or isolated to a specific request shape. - -When recording to Artillery Cloud, compare runs with the same `env` and `profile` tags. For example, compare `env:test,profile:smoke` runs to other test smoke runs rather than to local, load, or stress runs. Treat `stress.yml` failures as capacity signals rather than route contract failures unless the failing endpoint also fails smoke or contract checks. - -If Artillery fails with `SELF_SIGNED_CERT_IN_CHAIN` while recording to Artillery Cloud or calling an HTTPS deployment, configure Node with the same host CA bundle used for local Docker builds. `Dockerfile.local` receives that file through the `NPM_CA_CERT` environment variable and mounts it as a build secret at `/run/secrets/npm_ca`; local Artillery runs need the host path: - -```bash -export NODE_EXTRA_CA_CERTS="$NPM_CA_CERT" -``` +This helps most when multiple users submit the same expensive CPE search within the TTL. The first request is a cold cache miss; later identical requests on the same running task are warm cache hits. If identical cold requests arrive concurrently before the first one finishes, the later requests wait on the same candidate query instead of starting duplicate 10,000-candidate OpenSearch requests. Disconnecting one waiter does not cancel work still needed by another waiter; the shared operation is aborted when its final waiter disconnects or times out. Each active request still fetches its requested page of full CVE records after the shared candidate result is available. -Then rerun the Artillery command with `ARTILLERY_CLOUD_API_KEY`, `--record`, and the target environment variables. Do not commit Artillery Cloud keys, deployment URLs, or certificate file paths. +The cache does not share entries across ECS tasks. With multiple tasks, each task warms independently depending on load balancer routing. Steady repeated traffic can warm every active task, but unique one-off searches still pay the cold-cache cost. The request lifecycle and in-flight waiter registry are separate from this result cache, so the cache can later be replaced with Redis or ElastiCache without changing cancellation behavior. The per-process TTL, entry limit, cold-search concurrency, queue depth, queue wait, OpenSearch timeout, and overall request deadline can be tuned with `SearchPostFilterCacheTtlMs`, `SearchPostFilterCacheMaxEntries`, `SearchPostFilterColdSearchConcurrency`, `SearchPostFilterColdSearchMaxQueueDepth`, `SearchPostFilterColdSearchMaxWaitMs`, `SearchOpenSearchRequestTimeoutMs`, and `SearchRequestDeadlineMs`. Docker runtime smoke checks can be run after building the image. The default `Dockerfile` avoids BuildKit `RUN --mount` syntax for AWS builders that do not support it: diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 7db0faf..61279e4 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.0.2", "info": { - "version": "0.3.0-beta", + "version": "0.4.0-beta", "title": "CVE Search API", "description": "CVE Search API to retrieve all matching CVE records after applying request inputs as filters.", "contact": { @@ -25,13 +25,61 @@ "operationId": "search", "responses": { "200": { - "description": "A list of matching CVE records" + "description": "A list of matching CVE records", + "headers": { + "X-Request-ID": { + "description": "Request correlation ID", + "schema": { + "type": "string" + } + } + } }, "400": { - "description": "Bad Request" + "description": "Malformed JSON, invalid request body, parameter combination, or version range" }, "404": { "description": "Invalid cpeName value" + }, + "415": { + "description": "A non-empty request body did not use the application/json media type with UTF-8 encoding" + }, + "429": { + "description": "Application rate limit exceeded, or the cold CPE search queue is full or exceeded its maximum wait", + "headers": { + "Retry-After": { + "description": "Seconds until a rate-limited request can be retried", + "schema": { + "type": "integer" + } + }, + "RateLimit": { + "description": "Current application rate-limit state when application limiting is enabled", + "schema": { + "type": "string" + } + }, + "RateLimit-Policy": { + "description": "Application rate-limit policy when application limiting is enabled", + "schema": { + "type": "string" + } + } + } + }, + "503": { + "description": "OpenSearch is unavailable", + "headers": { + "Retry-After": { + "description": "Seconds before retrying an unavailable OpenSearch dependency", + "schema": { + "type": "integer" + } + } + } + }, + "504": { + "description": "Search exceeded its OpenSearch or overall processing deadline" } }, "requestBody": { @@ -60,7 +108,6 @@ "cvssV3_0Metrics": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "cvssV3_1Metrics": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", "cvssV4Metrics": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N", - "sourceIdentifier": "security@example.org", "cpeName": "cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*", "isVulnerable": false, "lastModStartDate": "2024-01-01T00:00:00.000Z", @@ -109,12 +156,57 @@ }, "/api/health-check": { "get": { - "summary": "Health check for AWS monitoring", - "description": "Health check for AWS monitoring", + "summary": "Process liveness check", + "description": "Returns 200 when the API process is running. This endpoint does not check OpenSearch.", "operationId": "healthCheck", "responses": { "200": { - "description": "OK" + "description": "OK", + "headers": { + "X-Request-ID": { + "description": "Request correlation ID", + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/api/ready-check": { + "get": { + "summary": "OpenSearch readiness check", + "description": "Sends a minimal zero-result search to the configured CVE index. Returns 200 when the index is available with search permission, or a sanitized 503 response when configuration, connectivity, timeout, permission, or index availability checks fail.", + "operationId": "readinessCheck", + "responses": { + "200": { + "description": "API is ready to serve search requests", + "headers": { + "X-Request-ID": { + "description": "Request correlation ID", + "schema": { + "type": "string" + } + } + } + }, + "503": { + "description": "Search service is not ready", + "headers": { + "Retry-After": { + "description": "Seconds before retrying the readiness check", + "schema": { + "type": "integer" + } + }, + "X-Request-ID": { + "description": "Request correlation ID", + "schema": { + "type": "string" + } + } + } } } } @@ -124,15 +216,29 @@ "tags": [ "CVE Record" ], - "summary": "Retrieves all matching CVE records after applying the JSON request body", - "description": "
Retrieves all matching CVE records after applying the JSON request body
", + "summary": "Deprecated raw query search", + "description": "Retrieves all matching CVE records after applying the JSON request body. This endpoint is deprecated and can be disabled by the service operator. Use POST /search instead.
", "operationId": "webSearch", "responses": { "200": { - "description": "A list of matching CVE records" + "description": "A list of matching CVE records", + "headers": { + "X-Request-ID": { + "description": "Request correlation ID", + "schema": { + "type": "string" + } + } + } }, "400": { "description": "Bad Request" + }, + "410": { + "description": "The deprecated endpoint is disabled; use POST /search" + }, + "504": { + "description": "Search exceeded its OpenSearch request deadline" } }, "requestBody": { @@ -183,7 +289,8 @@ } } } - } + }, + "deprecated": true } } }, @@ -196,8 +303,9 @@ "properties": { "keywordSearch": { "type": "string", + "maxLength": 512, "example": "remote code execution", - "description": "Returns CVE records where a word or phrase is included in the record's description. Multiple keywords operate like an AND statement, returning records whose descriptions include every keyword." + "description": "Returns CVE records where a word or phrase is included in the record's description. Multiple keywords operate like an AND statement, returning records whose descriptions include every keyword. Limited to 512 characters and 64 whitespace-delimited terms." }, "keywordExactMatch": { "type": "boolean", @@ -206,8 +314,9 @@ }, "cveIds": { "type": "string", + "maxLength": 4096, "example": "CVE-2024-1234,CVE-2024-5678", - "description": "Returns one or more CVE records identified by a comma-separated list of CVE IDs. Limited to 100 CVE IDs." + "description": "Returns one or more CVE records identified by a comma-separated list of CVE IDs. Limited to 100 CVE IDs, 64 characters per ID, and 4096 characters overall." }, "noRejected": { "type": "boolean", @@ -216,6 +325,7 @@ }, "cweId": { "type": "string", + "maxLength": 32, "example": "CWE-79", "description": "Returns CVE records that include the provided Common Weakness Enumeration ID." }, @@ -298,20 +408,16 @@ "example": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N", "description": "Returns CVE records that contain the provided CVSS v4.0 vector string. Values are limited to 256 characters, 32 slash-delimited metric tokens, and 32 characters per metric token." }, - "sourceIdentifier": { - "type": "string", - "example": "security@example.org", - "description": "Returns CVE records that contain the provided source identifier." - }, "cpeName": { "type": "string", + "maxLength": 2048, "example": "cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*", - "description": "Returns CVE records whose CPE Match Criteria apply to the provided CPE 2.3 asset name. The part, vendor, product, and version components must contain values other than *. The concrete version can match stored criteria with the same version or criteria with a wildcard version and version range fields. Invalid cpeName values return HTTP 404. Use virtualMatchString for partial CPE Match String searches." + "description": "Returns CVE records whose CPE Match Criteria apply to the provided CPE 2.3 asset name. CPE string literal comparisons are insensitive to lexical case. The part, vendor, product, and version components must contain values other than *. Every concrete non-version component must be compatible with the same CPE Match Criteria entry that satisfies the version and vulnerability checks; optional * components remain broad matches. The concrete version can match stored criteria with the same version or criteria with a wildcard version and version range fields. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. The version - represents the CPE logical value NA; numeric range boundaries on wildcard-version criteria do not exclude it. Invalid cpeName values return HTTP 404. Use virtualMatchString for partial CPE Match String searches." }, "isVulnerable": { "type": "boolean", "default": false, - "description": "When true, returns CVE records whose CPE Match Criteria match cpeName and are marked vulnerable. cpeName is required when isVulnerable is true. virtualMatchString cannot be provided with isVulnerable." + "description": "When true, returns CVE records where the same CPE Match Criteria entry both matches cpeName and is marked vulnerable. False or omitted does not apply a vulnerability restriction. cpeName is required when isVulnerable is true. virtualMatchString cannot be provided with isVulnerable." }, "lastModStartDate": { "type": "string", @@ -354,8 +460,9 @@ }, "versionStart": { "type": "string", + "maxLength": 256, "example": "2.2", - "description": "Sets the starting version bound for a CPE Match String range search. versionStartType and virtualMatchString are required when versionStart is provided. virtualMatchString must not include a version component." + "description": "Sets the starting version bound for a CPE Match String range search. versionStartType and virtualMatchString are required when versionStart is provided. When both bounds are provided, versionStart must be less than versionEnd; equal values require both bounds to be including. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. virtualMatchString must not include a version component." }, "versionStartType": { "type": "string", @@ -367,8 +474,9 @@ }, "versionEnd": { "type": "string", + "maxLength": 256, "example": "2.6", - "description": "Sets the ending version bound for a CPE Match String range search. versionEndType and virtualMatchString are required when versionEnd is provided. virtualMatchString must not include a version component." + "description": "Sets the ending version bound for a CPE Match String range search. versionEndType and virtualMatchString are required when versionEnd is provided. When both bounds are provided, versionStart must be less than versionEnd; equal values require both bounds to be including. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. virtualMatchString must not include a version component." }, "versionEndType": { "type": "string", @@ -380,8 +488,9 @@ }, "virtualMatchString": { "type": "string", + "maxLength": 2048, "example": "cpe:2.3:o:microsoft:windows_10_1809", - "description": "Returns CVE records whose CPE Match Criteria are compatible with the provided CPE 2.3 match string. Omitted trailing components and * components act as broad matches. When used with versionStart or versionEnd, provide a partial CPE 2.3 match string without a version component." + "description": "Returns CVE records whose CPE Match Criteria are compatible with the provided CPE 2.3 match string. CPE string literal comparisons are insensitive to lexical case. Omitted trailing components and * components act as broad matches. When used with versionStart or versionEnd, provide a partial CPE 2.3 match string without a version component." } } } diff --git a/api-docs/openapi.test.js b/api-docs/openapi.test.js index 9c3424c..0b49ee4 100644 --- a/api-docs/openapi.test.js +++ b/api-docs/openapi.test.js @@ -2,6 +2,7 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); const openapi = require('./openapi.json'); +const packageInfo = require('../package.json'); const constants = require('../utils/constants'); const { generateOpenApi } = require('../routes/swagger'); @@ -20,6 +21,7 @@ describe('OpenAPI contract', () => { test('documents the supported route surface', () => { expect(openapi.paths['/search'].post.operationId).toBe('search'); expect(openapi.paths['/api/health-check'].get.operationId).toBe('healthCheck'); + expect(openapi.paths['/api/ready-check'].get.operationId).toBe('readinessCheck'); expect(openapi.paths['/webSearch'].post.operationId).toBe('webSearch'); }); @@ -32,6 +34,10 @@ describe('OpenAPI contract', () => { ]); }); + test('uses the project package version', () => { + expect(openapi.info.version).toBe(packageInfo.version); + }); + test('all path parameter references resolve to component parameters', () => { Object.values(openapi.paths).forEach((pathItem) => { Object.values(pathItem).forEach((operation) => { @@ -60,16 +66,41 @@ describe('OpenAPI contract', () => { expect(schema.properties.cveIds.type).toBe('string'); expect(schema.properties.cveIds.description).toContain('comma-separated list of CVE IDs'); expect(schema.properties.cveIds.description).toContain(`${constants.MAX_CVE_IDS} CVE IDs`); + expect(schema.properties.cveIds.description).toContain(`${constants.MAX_CVE_ID_LENGTH} characters per ID`); + expect(schema.properties.keywordSearch.description).toContain( + `${constants.MAX_KEYWORD_SEARCH_TOKENS} whitespace-delimited terms` + ); + Object.entries(constants.SEARCH_STRING_LENGTH_LIMITS).forEach(([field, maxLength]) => { + expect(schema.properties[field].maxLength).toBe(maxLength); + }); expect(schema.properties.noRejected.type).toBe('boolean'); expect(schema.properties.noRejected.description).toContain('When true'); + expect(schema.properties.isVulnerable.description).toContain('same CPE Match Criteria entry'); + expect(schema.properties.isVulnerable.description).toContain('False or omitted'); + expect(schema.properties.cpeName.description).toContain('Maven-style ordering'); + expect(schema.properties.cpeName.description).toContain( + 'Every concrete non-version component must be compatible with the same CPE Match Criteria entry' + ); + expect(schema.properties.cpeName.description).toContain('optional * components remain broad matches'); + expect(schema.properties.cpeName.description).toContain('insensitive to lexical case'); + expect(schema.properties.cpeName.description).toContain('exact CPE Match Criteria preserve leading zeros'); expect(schema.properties.versionStartType.enum).toStrictEqual(constants.CPE_VERSION_RANGE_TYPES); expect(schema.properties.versionEndType.enum).toStrictEqual(constants.CPE_VERSION_RANGE_TYPES); expect(schema.properties.resultsPerPage.maximum).toBe(constants.MAX_RESULTS_LIMIT); expect(schema.properties.cveId).toBeUndefined(); + expect(schema.properties.sourceIdentifier).toBeUndefined(); expect(schema.properties.versionStart.description).toContain('versionStartType'); + expect(schema.properties.versionStart.description).toContain('less than versionEnd'); + expect(schema.properties.versionStart.description).toContain('ranges ignore leading zeros'); + expect(schema.properties.versionStart.description).toContain('Maven-style ordering'); expect(schema.properties.versionEnd.description).toContain('versionEndType'); + expect(schema.properties.versionEnd.description).toContain('exact CPE Match Criteria preserve leading zeros'); + expect(schema.properties.versionEnd.description).toContain( + 'prerelease qualifiers before an unqualified release' + ); expect(schema.properties.virtualMatchString.description).toContain('CPE Match Criteria'); expect(schema.properties.virtualMatchString.description).toContain('compatible'); + expect(schema.properties.virtualMatchString.description).toContain('insensitive to lexical case'); expect(schema.properties.virtualMatchString.description).toContain('without a version component'); expect(schema.properties.virtualMatchString.example).toBe('cpe:2.3:o:microsoft:windows_10_1809'); expect(Object.keys(mediaType.examples)).toStrictEqual([ @@ -85,6 +116,8 @@ describe('OpenAPI contract', () => { const operation = openapi.paths['/webSearch'].post; const schema = operation.requestBody.content['application/json'].schema; + expect(operation.deprecated).toBe(true); + expect(operation.description).toContain('Use POST /search instead'); expect(operation.parameters || []).toStrictEqual([]); expect(operation.requestBody.required).toBe(true); expect(schema.required).toStrictEqual(['query']); @@ -93,6 +126,7 @@ describe('OpenAPI contract', () => { expect(schema.properties.sort.additionalProperties).toBe(false); expect(schema.properties.sort.properties.property.enum).toStrictEqual(['cveId', 'relevancy']); expect(schema.properties.size.maximum).toBe(constants.MAX_RESULTS_LIMIT); + expect(operation.responses['410'].description).toContain('use POST /search'); }); test('documents cpeName as a CPE Match Criteria filter with required components', () => { @@ -101,10 +135,42 @@ describe('OpenAPI contract', () => { expect(cpeName.description).toContain('CPE Match Criteria'); expect(cpeName.description).toContain('part, vendor, product, and version'); expect(cpeName.description).toContain('version range fields'); + expect(cpeName.description).toContain('insensitive to lexical case'); + expect(cpeName.description).toContain('exact CPE Match Criteria preserve leading zeros'); + expect(cpeName.description).toContain('logical value NA'); + expect(cpeName.description).toContain('numeric range boundaries'); expect(cpeName.description).toContain('HTTP 404'); expect(cpeName.description).toContain('virtualMatchString'); expect(cpeName.example).toBe('cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*'); expect(openapi.paths['/search'].post.responses['404'].description).toBe('Invalid cpeName value'); + expect(openapi.paths['/search'].post.responses['429'].description).toContain('rate limit'); + }); + + test('documents fail-closed request and dependency responses for POST /search', () => { + const responses = openapi.paths['/search'].post.responses; + + expect(responses['400'].description).toContain('Malformed JSON'); + expect(responses['415'].description).toContain('application/json'); + expect(responses['503'].description).toContain('OpenSearch'); + expect(responses['503'].headers['Retry-After']).toStrictEqual({ + description: 'Seconds before retrying an unavailable OpenSearch dependency', + schema: { + type: 'integer' + } + }); + }); + + test('documents request correlation headers', () => { + expect(openapi.paths['/search'].post.responses['200'].headers['X-Request-ID']).toStrictEqual({ + description: 'Request correlation ID', + schema: { + type: 'string' + } + }); + expect(openapi.paths['/api/health-check'].get.responses['200'].headers['X-Request-ID']).toBeDefined(); + expect(openapi.paths['/api/ready-check'].get.responses['200'].headers['X-Request-ID']).toBeDefined(); + expect(openapi.paths['/api/ready-check'].get.responses['503'].headers['Retry-After']).toBeDefined(); + expect(openapi.paths['/webSearch'].post.responses['200'].headers['X-Request-ID']).toBeDefined(); }); test('committed OpenAPI artifact matches generated route documentation', async () => { diff --git a/config/custom-environment-variables.jsonc b/config/custom-environment-variables.jsonc index 9dc44fc..3470a97 100644 --- a/config/custom-environment-variables.jsonc +++ b/config/custom-environment-variables.jsonc @@ -15,7 +15,25 @@ "postFilterCacheTtlMs": "SearchPostFilterCacheTtlMs", "postFilterCacheMaxEntries": "SearchPostFilterCacheMaxEntries", "postFilterColdSearchConcurrency": "SearchPostFilterColdSearchConcurrency", - "postFilterColdSearchMaxQueueDepth": "SearchPostFilterColdSearchMaxQueueDepth" + "postFilterColdSearchMaxQueueDepth": "SearchPostFilterColdSearchMaxQueueDepth", + "postFilterColdSearchMaxWaitMs": "SearchPostFilterColdSearchMaxWaitMs", + "openSearchRequestTimeoutMs": "SearchOpenSearchRequestTimeoutMs", + "requestDeadlineMs": "SearchRequestDeadlineMs", + "readinessTimeoutMs": "SearchReadinessTimeoutMs" + }, + "requestLogging": { + "successSampleRate": "RequestLogSuccessSampleRate", + "healthChecks": "RequestLogHealthChecks" + }, + "features": { + "webSearchEnabled": "WebSearchEnabled" + }, + "rateLimiting": { + "search": { + "enabled": "SearchRateLimitEnabled", + "windowMs": "SearchRateLimitWindowMs", + "maxRequests": "SearchRateLimitMaxRequests" + } }, // constants for testing node-config // these values are only used to test node-config in AppConfig.test.int.ts diff --git a/config/default.jsonc b/config/default.jsonc index 136c718..8cdef78 100644 --- a/config/default.jsonc +++ b/config/default.jsonc @@ -22,7 +22,37 @@ // Limits concurrent cold CPE candidate OpenSearch requests per Node.js process. "postFilterColdSearchConcurrency": "10", // Limits queued cold CPE candidate searches waiting behind the concurrency cap per Node.js process. - "postFilterColdSearchMaxQueueDepth": "500" + "postFilterColdSearchMaxQueueDepth": "500", + // Limits how long a cold CPE candidate search can wait in the per-process queue. + "postFilterColdSearchMaxWaitMs": "10000", + // Limits each request from the API process to OpenSearch. + "openSearchRequestTimeoutMs": "20000", + // Limits total processing time for a /search request. + "requestDeadlineMs": "30000", + // Limits the minimal OpenSearch request used by /api/ready-check. + "readinessTimeoutMs": "5000" + }, + // Access logging settings. Error responses are always logged. + "requestLogging": { + // Fraction of successful non-health-check requests to log, from 0 through 1. + "successSampleRate": "0.1", + // Set to TRUE to retain successful health-check access logs. + "healthChecks": "FALSE" + }, + // Feature flags used for controlled endpoint migrations. + "features": { + // Keep enabled until existing consumers have migrated to POST /search. + "webSearchEnabled": "TRUE" + }, + // Application-level fallback protection. Infrastructure-level enforcement remains preferred. + "rateLimiting": { + "search": { + // Disabled until a per-task threshold is approved. + "enabled": "FALSE", + "windowMs": "60000", + // A positive value is required when enabled. + "maxRequests": "0" + } } } } diff --git a/config/runtime-config.test.js b/config/runtime-config.test.js index 24d9b3d..350a6b1 100644 --- a/config/runtime-config.test.js +++ b/config/runtime-config.test.js @@ -95,6 +95,20 @@ describe('runtime container and compose configuration', () => { expect(customEnvironmentVariables).toContain( '"postFilterColdSearchMaxQueueDepth": "SearchPostFilterColdSearchMaxQueueDepth"' ); + expect(customEnvironmentVariables).toContain( + '"postFilterColdSearchMaxWaitMs": "SearchPostFilterColdSearchMaxWaitMs"' + ); + expect(customEnvironmentVariables).toContain( + '"openSearchRequestTimeoutMs": "SearchOpenSearchRequestTimeoutMs"' + ); + expect(customEnvironmentVariables).toContain('"requestDeadlineMs": "SearchRequestDeadlineMs"'); + expect(customEnvironmentVariables).toContain('"readinessTimeoutMs": "SearchReadinessTimeoutMs"'); + expect(customEnvironmentVariables).toContain('"successSampleRate": "RequestLogSuccessSampleRate"'); + expect(customEnvironmentVariables).toContain('"healthChecks": "RequestLogHealthChecks"'); + expect(customEnvironmentVariables).toContain('"webSearchEnabled": "WebSearchEnabled"'); + expect(customEnvironmentVariables).toContain('"enabled": "SearchRateLimitEnabled"'); + expect(customEnvironmentVariables).toContain('"windowMs": "SearchRateLimitWindowMs"'); + expect(customEnvironmentVariables).toContain('"maxRequests": "SearchRateLimitMaxRequests"'); }); test.each([['config/default.jsonc'], ['config/devel.jsonc'], ['config/prod.jsonc']])( diff --git a/controllers/middleware.js b/controllers/middleware.js index 446634f..8eedf02 100644 --- a/controllers/middleware.js +++ b/controllers/middleware.js @@ -90,6 +90,10 @@ function hasInvalidCvssMetricsShape(value) { ); } +function countWhitespaceDelimitedTokens(value) { + return value.trim().split(/\s+/).length; +} + function parseCveIds(value) { if (typeof value !== 'string') return null; @@ -142,11 +146,29 @@ function validateParams(req, res, next) { } } + for (const [param, maxLength] of Object.entries(constants.SEARCH_STRING_LENGTH_LIMITS)) { + if (typeof params[param] === 'string' && params[param].length > maxLength) { + return res.status(400).json(error.searchFieldLengthError(param, maxLength)); + } + } + + if ( + typeof params.keywordSearch === 'string' && + countWhitespaceDelimitedTokens(params.keywordSearch) > constants.MAX_KEYWORD_SEARCH_TOKENS + ) { + return res + .status(400) + .json(error.searchFieldTokenLimitError('keywordSearch', constants.MAX_KEYWORD_SEARCH_TOKENS)); + } + if (hasParam(params, 'cveIds')) { const cveIds = parseCveIds(params.cveIds); if (!cveIds) { return res.status(400).json(error.cveIdsFormatError()); } + if (cveIds.some((cveId) => cveId.length > constants.MAX_CVE_ID_LENGTH)) { + return res.status(400).json(error.cveIdLengthError()); + } if (cveIds.length > constants.MAX_CVE_IDS) { return res.status(400).json(error.cveIdsLimitError()); } @@ -193,6 +215,18 @@ function validateParams(req, res, next) { if (virtualMatchString.hasVersionComponent) { return res.status(400).json(error.virtualMatchStringVersionComponentError()); } + + if (hasParam(params, 'versionStart') && hasParam(params, 'versionEnd')) { + const comparison = cpe.compareCpeVersions(params.versionStart, params.versionEnd); + const hasEmptyEqualRange = + comparison === 0 && + (params.versionStartType.toLowerCase() !== 'including' || + params.versionEndType.toLowerCase() !== 'including'); + + if (comparison > 0 || hasEmptyEqualRange) { + return res.status(400).json(error.versionRangeOrderError()); + } + } } for (const { param, allowed, buildError } of severityValidators) { @@ -224,7 +258,7 @@ function validateParams(req, res, next) { return res.status(400).json(error.missingCpeNameError()); } - //Ensures cpeName follows NVD API requirements for required CPE components + //Ensures cpeName contains the required concrete CPE components if (params?.cpeName && !cpe.isValidCpeName(params.cpeName)) { return res.status(404).json(error.invalidCpeNameError()); } diff --git a/controllers/middleware.test.js b/controllers/middleware.test.js index c4df38f..5079be4 100644 --- a/controllers/middleware.test.js +++ b/controllers/middleware.test.js @@ -1,4 +1,5 @@ const mw = require('./middleware.js'); +const constants = require('../utils/constants'); describe('middleware validation function', () => { let req, res, next, status, json; @@ -30,6 +31,10 @@ describe('middleware validation function', () => { expect(next.mock.calls[0]).toHaveLength(0); } + function padBetween(prefix, suffix, length, character = 'a') { + return `${prefix}${character.repeat(length - prefix.length - suffix.length)}${suffix}`; + } + test('ValidateParams returns an error when keywordExactMatch is true without keywordSearch', () => { validate({ keywordExactMatch: true @@ -144,7 +149,7 @@ describe('middleware validation function', () => { ); }); - test('ValidateParams calls next() for broad NVD-style virtualMatchString values', () => { + test('ValidateParams accepts broad virtualMatchString values', () => { validate({ virtualMatchString: 'cpe:2.3:o:*:windows_10_1809' }); @@ -172,6 +177,82 @@ describe('middleware validation function', () => { expectValidationSuccess(); }); + test('ValidateParams returns an error when versionStart exceeds versionEnd', () => { + validate({ + virtualMatchString: 'cpe:2.3:o:linux:linux_kernel', + versionStart: '3.0', + versionStartType: 'including', + versionEnd: '2.0', + versionEndType: 'including' + }); + + expectValidationError( + 'VERSION_RANGE_ORDER', + 'The versionStart parameter must be less than versionEnd. Equal values require both bounds to be including.' + ); + }); + + test.each([ + ['excluding', 'including'], + ['including', 'excluding'], + ['excluding', 'excluding'] + ])( + 'ValidateParams returns an error for an empty equal version range with %s/%s bounds', + (versionStartType, versionEndType) => { + validate({ + virtualMatchString: 'cpe:2.3:o:linux:linux_kernel', + versionStart: '2.0', + versionStartType, + versionEnd: '2.0', + versionEndType + }); + + expectValidationError( + 'VERSION_RANGE_ORDER', + 'The versionStart parameter must be less than versionEnd. Equal values require both bounds to be including.' + ); + } + ); + + test('ValidateParams accepts an equal inclusive version range', () => { + validate({ + virtualMatchString: 'cpe:2.3:o:linux:linux_kernel', + versionStart: '2.0', + versionStartType: 'including', + versionEnd: '2.0', + versionEndType: 'including' + }); + + expectValidationSuccess(); + }); + + test('ValidateParams accepts a prerelease-to-release version range', () => { + validate({ + virtualMatchString: 'cpe:2.3:a:vendor:product', + versionStart: '1.0.0-rc1', + versionStartType: 'including', + versionEnd: '1.0.0', + versionEndType: 'including' + }); + + expectValidationSuccess(); + }); + + test('ValidateParams rejects a release-to-prerelease version range', () => { + validate({ + virtualMatchString: 'cpe:2.3:a:vendor:product', + versionStart: '1.0.0', + versionStartType: 'including', + versionEnd: '1.0.0-rc1', + versionEndType: 'including' + }); + + expectValidationError( + 'VERSION_RANGE_ORDER', + 'The versionStart parameter must be less than versionEnd. Equal values require both bounds to be including.' + ); + }); + test('ValidateParams returns an error when version range params are used without virtualMatchString', () => { validate({ versionStart: '2.2', @@ -316,7 +397,6 @@ describe('middleware validation function', () => { ['cvssV3_0Metrics', 'CVSS:3.0/AV:N/AC:L'], ['cvssV3_1Metrics', 'CVSS:3.1/PR:N/UI:R'], ['cvssV4Metrics', 'CVSS:4.0/AV:N/AC:L'], - ['sourceIdentifier', 'CISA'], ['cpeName', 'cpe:2.3:a:vendor:product:1.0:*:*:*:*:*:*:*'], ['isVulnerable', 'false'], ['lastModStartDate', '2024-01-01T00:00:00.000Z'], @@ -334,12 +414,15 @@ describe('middleware validation function', () => { expectValidationSuccess(); }); - test('ValidateParams returns an error when deprecated cveId is provided.', () => { + test.each([ + ['cveId', 'CVE-2024-0001'], + ['sourceIdentifier', 'CISA'] + ])('ValidateParams returns an error when unsupported %s is provided.', (param, value) => { validate({ - cveId: 'CVE-2024-0001' + [param]: value }); - expectValidationError('INVALID_PARAM', 'cveId is not supported by the Search API.'); + expectValidationError('INVALID_PARAM', `${param} is not supported by the Search API.`); }); test.each([ @@ -364,6 +447,107 @@ describe('middleware validation function', () => { expectValidationError('CVE_IDS_LIMIT', 'The cveIds parameter cannot contain more than 100 CVE IDs.'); }); + test.each(Object.entries(constants.SEARCH_STRING_LENGTH_LIMITS))( + 'ValidateParams returns an error when %s exceeds its maximum length.', + (param, maxLength) => { + validate({ + [param]: 'a'.repeat(maxLength + 1) + }); + + expectValidationError( + 'SEARCH_FIELD_LENGTH', + `The ${param} field must be ${maxLength} characters or fewer.` + ); + } + ); + + test.each([ + ['keywordSearch', 'a'.repeat(constants.SEARCH_STRING_LENGTH_LIMITS.keywordSearch)], + ['cweId', 'a'.repeat(constants.SEARCH_STRING_LENGTH_LIMITS.cweId)] + ])('ValidateParams accepts %s at its maximum length.', (param, value) => { + validate({ + [param]: value + }); + + expectValidationSuccess(); + }); + + test('ValidateParams accepts cveIds and each CVE ID at their maximum lengths.', () => { + const idPrefix = 'CVE-2024-'; + const longId = `${idPrefix}${'1'.repeat(constants.MAX_CVE_ID_LENGTH - idPrefix.length)}`; + const shortId = `${idPrefix}${'2'.repeat(32 - idPrefix.length)}`; + const mediumId = `${idPrefix}${'3'.repeat(33 - idPrefix.length)}`; + const cveIds = [...Array(62).fill(longId), shortId, mediumId].join(','); + + expect(cveIds).toHaveLength(constants.SEARCH_STRING_LENGTH_LIMITS.cveIds); + validate({ cveIds }); + + expectValidationSuccess(); + }); + + test('ValidateParams returns an error when an individual CVE ID exceeds its maximum length.', () => { + const idPrefix = 'CVE-2024-'; + + validate({ + cveIds: `${idPrefix}${'1'.repeat(constants.MAX_CVE_ID_LENGTH - idPrefix.length + 1)}` + }); + + expectValidationError( + 'CVE_ID_LENGTH', + `Each CVE ID in the cveIds field must be ${constants.MAX_CVE_ID_LENGTH} characters or fewer.` + ); + }); + + test.each([ + [ + 'cpeName', + padBetween('cpe:2.3:a:', ':product:1.0:*:*:*:*:*:*:*', constants.SEARCH_STRING_LENGTH_LIMITS.cpeName) + ], + [ + 'virtualMatchString', + padBetween('cpe:2.3:a:', ':product', constants.SEARCH_STRING_LENGTH_LIMITS.virtualMatchString) + ] + ])('ValidateParams accepts %s at its maximum length.', (param, value) => { + validate({ + [param]: value + }); + + expectValidationSuccess(); + }); + + test.each(['versionStart', 'versionEnd'])('ValidateParams accepts %s at its maximum length.', (versionParam) => { + const typeParam = `${versionParam}Type`; + + validate({ + virtualMatchString: 'cpe:2.3:a:vendor:product', + [versionParam]: '1'.repeat(constants.SEARCH_STRING_LENGTH_LIMITS[versionParam]), + [typeParam]: 'including' + }); + + expectValidationSuccess(); + }); + + test('ValidateParams accepts keywordSearch at the maximum term count.', () => { + validate({ + keywordSearch: Array(constants.MAX_KEYWORD_SEARCH_TOKENS).fill('a').join(' ') + }); + + expectValidationSuccess(); + }); + + test('ValidateParams returns an error when keywordSearch exceeds the maximum term count.', () => { + validate({ + keywordSearch: Array(constants.MAX_KEYWORD_SEARCH_TOKENS + 1) + .fill('a') + .join(' ') + }); + + expectValidationError( + 'SEARCH_FIELD_TOKEN_LIMIT', + `The keywordSearch field must contain ${constants.MAX_KEYWORD_SEARCH_TOKENS} or fewer terms.` + ); + }); + test.each([ ['true string', 'true'], ['false string', 'false'], diff --git a/controllers/searchController.js b/controllers/searchController.js index b94448d..5deabdb 100644 --- a/controllers/searchController.js +++ b/controllers/searchController.js @@ -2,11 +2,15 @@ const PackageInfo = require('cve-core').packageInfo; const errors = require('../utils/errors'); const cpe = require('../utils/cpe'); const searchPerformance = require('../utils/searchPerformance'); +const searchDeadline = require('../utils/searchDeadline'); const searchPostFilterCache = require('../utils/searchPostFilterCache'); +const searchInFlightRegistry = require('../utils/searchInFlightRegistry'); const searchPostFilterWorkQueue = require('../utils/searchPostFilterWorkQueue'); const searchProvider = require('../utils/searchProvider'); const searchQueryBuilder = require('../utils/searchQueryBuilder'); const error = new errors.errors(); +const openSearchUnavailableErrorNames = new Set(['ConnectionError', 'NoLivingConnectionsError']); +const openSearchUnavailableStatusCodes = new Set([502, 503, 504]); function hasSearchPostFilters(postFilters) { return Boolean(postFilters?.cpeName || postFilters?.virtualMatchString || postFilters?.virtualMatchStringRange); @@ -32,7 +36,12 @@ function filterSearchHits(resp, postFilters) { hitsContainer, candidateCount: hitsContainer.hits.length, filteredHits: hitsContainer.hits.filter((hit) => { - if (postFilters?.cpeName && !cpe.hitAppliesToCpeName(hit, postFilters.cpeName, cpeEvaluationContext)) { + if ( + postFilters?.cpeName && + !cpe.hitAppliesToCpeName(hit, postFilters.cpeName, cpeEvaluationContext, { + requireVulnerable: postFilters.isVulnerable === true + }) + ) { return false; } @@ -90,6 +99,18 @@ function buildPostFilteredResponse( return buildPagedSearchResponse(resp, filteredHits.length, pagedHits); } +function getOpenSearchErrorStatusCode(err) { + return Number(err?.statusCode ?? err?.meta?.statusCode ?? err?.meta?.body?.status); +} + +function isOpenSearchUnavailableError(err) { + return ( + searchProvider.isSearchProviderContractError?.(err) === true || + openSearchUnavailableErrorNames.has(err?.name) || + openSearchUnavailableStatusCodes.has(getOpenSearchErrorStatusCode(err)) + ); +} + function buildWebSearchSort(sort) { if (!sort) return undefined; @@ -153,20 +174,32 @@ function combineTook(...values) { return numericValues.reduce((sum, value) => sum + value, 0); } -async function executeStandardSearch( - searchManager, - searchOptions, - queryStringObjects, - postFilters, - responseOptions, - metrics -) { +function recordOpenSearchStart(metrics, operation, state) { + metrics.openSearchOperations ??= []; + metrics.openSearchOperations.push(operation); + metrics.maxOpenSearchActiveRequests = Math.max(metrics.maxOpenSearchActiveRequests ?? 0, state.activeCount); +} + +async function executeOpenSearch(body, operation, signal, metrics) { + return searchProvider.searchOpenSearch(body, { + signal, + operation, + onStart: (state) => recordOpenSearchStart(metrics, operation, state) + }); +} + +async function executeStandardSearch(searchOptions, queryStringObjects, postFilters, responseOptions, metrics, signal) { metrics.strategy = 'standard'; const searchStart = searchPerformance.markTime(); - const searchResp = await searchManager.apiSearch(null, searchOptions, queryStringObjects); + const body = searchQueryBuilder.buildOpenSearchBody(searchOptions, queryStringObjects); + const searchData = await executeOpenSearch(body, 'standard', signal, metrics); + const searchResp = { + status: 'ok', + data: searchData + }; metrics.openSearchElapsedMs = searchPerformance.elapsedMs(searchStart); - metrics.openSearchTookMs = searchResp?.data?.took; + metrics.openSearchTookMs = searchData?.took; const postFilterStart = searchPerformance.markTime(); const resp = applySearchPostFilters(searchResp, postFilters, responseOptions, metrics); @@ -175,39 +208,38 @@ async function executeStandardSearch( return resp; } -async function fetchFullHitsByCveIds(searchManager, cveIds, metrics) { +async function fetchFullHitsByCveIds(cveIds, metrics, signal) { if (!cveIds.length) return []; - const cacheKey = searchPostFilterCache.buildInFlightKey('pageFetch', cveIds); - const inFlightResult = searchPostFilterCache.getInFlight(cacheKey); - if (inFlightResult) { - metrics.pageFetchInFlightHit = true; + const inFlightKey = searchInFlightRegistry.buildKey('pageFetch', cveIds); + const waitStart = searchPerformance.markTime(); + const inFlight = searchInFlightRegistry.acquire( + inFlightKey, + async (operationSignal) => { + const { searchOptions, queryStringObjects } = searchQueryBuilder.buildCveIdsSearchRequest(cveIds); + const body = searchQueryBuilder.buildOpenSearchBody(searchOptions, queryStringObjects); + const pageFetchStart = searchPerformance.markTime(); + const pageData = await executeOpenSearch(body, 'page-fetch', operationSignal, metrics); + metrics.pageFetchElapsedMs = searchPerformance.elapsedMs(pageFetchStart); + metrics.pageFetchTookMs = pageData?.took; + + return orderHitsByCveIds(pageData?.hits?.hits ?? [], cveIds); + }, + { signal } + ); - const waitStart = searchPerformance.markTime(); - const result = await inFlightResult; + metrics.pageFetchInFlightHit = inFlight.shared; + const result = await inFlight.promise; + if (inFlight.shared) { metrics.pageFetchWaitElapsedMs = searchPerformance.elapsedMs(waitStart); - - return result; } - metrics.pageFetchInFlightHit = false; - - const { searchOptions, queryStringObjects } = searchQueryBuilder.buildCveIdsSearchRequest(cveIds); - const promise = (async () => { - const pageFetchStart = searchPerformance.markTime(); - const pageResp = await searchManager.apiSearch(null, searchOptions, queryStringObjects); - metrics.pageFetchElapsedMs = searchPerformance.elapsedMs(pageFetchStart); - metrics.pageFetchTookMs = pageResp?.data?.took; - - return orderHitsByCveIds(pageResp?.data?.hits?.hits ?? [], cveIds); - })(); - - return searchPostFilterCache.setInFlight(cacheKey, promise); + return result; } -async function buildPostFilterCandidateResult(candidateBody, postFilters, metrics) { +async function buildPostFilterCandidateResult(candidateBody, postFilters, metrics, signal) { const candidateStart = searchPerformance.markTime(); - const candidateData = await searchProvider.searchRaw(candidateBody); + const candidateData = await executeOpenSearch(candidateBody, 'cpe-candidate', signal, metrics); metrics.openSearchElapsedMs = searchPerformance.elapsedMs(candidateStart); metrics.openSearchTookMs = candidateData?.took; @@ -225,7 +257,7 @@ async function buildPostFilterCandidateResult(candidateBody, postFilters, metric }; } -async function getPostFilterCandidateResult(cacheKey, candidateBody, postFilters, metrics) { +async function getPostFilterCandidateResult(cacheKey, candidateBody, postFilters, metrics, signal) { const cachedResult = searchPostFilterCache.get(cacheKey); if (cachedResult) { metrics.cacheHit = true; @@ -233,55 +265,58 @@ async function getPostFilterCandidateResult(cacheKey, candidateBody, postFilters return cachedResult; } - const inFlightResult = searchPostFilterCache.getInFlight(cacheKey); - if (inFlightResult) { - metrics.cacheHit = false; - metrics.inFlightHit = true; + metrics.cacheHit = false; + const inFlightKey = searchInFlightRegistry.buildKey('candidate', cacheKey); + const queueStart = searchPerformance.markTime(); + const waitStart = searchPerformance.markTime(); + const inFlight = searchInFlightRegistry.acquire( + inFlightKey, + (operationSignal) => + searchPostFilterWorkQueue + .run( + () => { + metrics.coldSearchQueueWaitElapsedMs = searchPerformance.elapsedMs(queueStart); + metrics.coldSearchQueueState = searchPostFilterWorkQueue.getState(); + return buildPostFilterCandidateResult(candidateBody, postFilters, metrics, operationSignal); + }, + { signal: operationSignal } + ) + .then((result) => { + searchPostFilterCache.set(cacheKey, result); + return result; + }), + { signal } + ); - const waitStart = searchPerformance.markTime(); - const result = await inFlightResult; + metrics.inFlightHit = inFlight.shared; + const result = await inFlight.promise; + if (inFlight.shared) { metrics.inFlightWaitElapsedMs = searchPerformance.elapsedMs(waitStart); - - return result; } - metrics.cacheHit = false; - metrics.inFlightHit = false; - - const queueStart = searchPerformance.markTime(); - const promise = searchPostFilterWorkQueue - .run(() => { - metrics.coldSearchQueueWaitElapsedMs = searchPerformance.elapsedMs(queueStart); - metrics.coldSearchQueueState = searchPostFilterWorkQueue.getState(); - return buildPostFilterCandidateResult(candidateBody, postFilters, metrics); - }) - .then((result) => { - searchPostFilterCache.set(cacheKey, result); - return result; - }); - - return searchPostFilterCache.setInFlight(cacheKey, promise); + return result; } async function executeOptimizedPostFilterSearch( - searchManager, searchOptions, queryStringObjects, postFilters, responseOptions, - metrics + metrics, + signal ) { metrics.strategy = 'cpe-candidate'; const candidateBody = searchQueryBuilder.buildCpeCandidateSearchBody(searchOptions, queryStringObjects); const cacheKey = searchPostFilterCache.buildCacheKey(candidateBody, postFilters); - const candidateResult = await getPostFilterCandidateResult(cacheKey, candidateBody, postFilters, metrics); + const candidateResult = await getPostFilterCandidateResult(cacheKey, candidateBody, postFilters, metrics, signal); + searchDeadline.throwIfAborted(signal); const pageCveIds = paginateHits(candidateResult.filteredCveIds, responseOptions); metrics.candidateCount = candidateResult.candidateCount; metrics.filteredCount = candidateResult.filteredCount ?? candidateResult.filteredCveIds.length; metrics.returnedCount = pageCveIds.length; - const fullPageHits = await fetchFullHitsByCveIds(searchManager, pageCveIds, metrics); + const fullPageHits = await fetchFullHitsByCveIds(pageCveIds, metrics, signal); const response = { data: { took: combineTook(metrics.openSearchTookMs, metrics.pageFetchTookMs), @@ -297,55 +332,61 @@ async function executeOptimizedPostFilterSearch( return buildPagedSearchResponse(response, metrics.filteredCount, fullPageHits); } -async function executeSearch(searchManager, searchOptions, queryStringObjects, postFilters, responseOptions, metrics) { +async function executeSearch(searchOptions, queryStringObjects, postFilters, responseOptions, metrics, signal) { if (!hasSearchPostFilters(postFilters)) { - return executeStandardSearch( - searchManager, - searchOptions, - queryStringObjects, - postFilters, - responseOptions, - metrics - ); + return executeStandardSearch(searchOptions, queryStringObjects, postFilters, responseOptions, metrics, signal); } return executeOptimizedPostFilterSearch( - searchManager, searchOptions, queryStringObjects, postFilters, responseOptions, - metrics + metrics, + signal ); } async function searchHandler(req, res, next) { const params = req.searchParams ?? req.query ?? {}; - const metrics = {}; + const metrics = { + requestId: req.requestId + }; const handlerStart = searchPerformance.markTime(); + const lifecycle = searchDeadline.createSearchRequestLifecycle( + req, + res, + searchProvider.getSearchRequestDeadlineMs() + ); + let searchOptions; + let postFilters; + let responseOptions; try { - const searchManager = searchProvider.getAdvancedSearchManager(); - const { searchOptions, queryStringObjects, postFilters, responseOptions } = - searchQueryBuilder.buildSearchRequest(params); + const searchRequest = searchQueryBuilder.buildSearchRequest(params); + searchOptions = searchRequest.searchOptions; + postFilters = searchRequest.postFilters; + responseOptions = searchRequest.responseOptions; const resp = await executeSearch( - searchManager, searchOptions, - queryStringObjects, + searchRequest.queryStringObjects, postFilters, responseOptions, - metrics + metrics, + lifecycle.signal ); + lifecycle.throwIfAborted(); const pagingOptions = responseOptions ?? searchOptions; - metrics.totalElapsedMs = searchPerformance.elapsedMs(handlerStart); - searchPerformance.logSearchPerformance(metrics, searchOptions, responseOptions, postFilters); + metrics.outcome = 'success'; + metrics.statusCode = 200; //Calculate current page and total pages to append to JSON output const totalValue = resp.data.hits.total.value; - let pageSize = Math.min(totalValue, pagingOptions.resultsPerPage); - const page = pageSize === 0 ? 0 : Math.floor(pagingOptions.from / pageSize) + 1; - const totalPages = pageSize === 0 ? 0 : Math.ceil(totalValue / pageSize); + const requestedPageSize = pagingOptions.resultsPerPage; + const pageSize = Math.min(totalValue, requestedPageSize); + const page = totalValue === 0 ? 0 : Math.floor(pagingOptions.from / requestedPageSize) + 1; + const totalPages = totalValue === 0 ? 0 : Math.ceil(totalValue / requestedPageSize); return res.status(200).json({ page, @@ -354,17 +395,55 @@ async function searchHandler(req, res, next) { ...resp }); } catch (err) { + if (searchDeadline.isSearchClientDisconnectedError(err)) { + metrics.outcome = 'client_disconnected'; + metrics.statusCode = 499; + metrics.abortReason = 'client_disconnect'; + return; + } + if (searchPostFilterWorkQueue.isQueueFullError(err)) { + metrics.outcome = 'queue_full'; + metrics.statusCode = 429; + metrics.coldSearchQueueState = err.state; if (typeof res.set === 'function') res.set('Retry-After', '5'); return res.status(429).json(error.searchQueueFullError()); } - if (err.name === 'ConnectionError') { - return res.status(400).json(error.openSearchError()); + if (searchPostFilterWorkQueue.isQueueTimeoutError(err)) { + metrics.outcome = 'queue_timeout'; + metrics.statusCode = 429; + metrics.coldSearchQueueState = err.state; + metrics.coldSearchQueueWaitElapsedMs = err.waitElapsedMs; + if (typeof res.set === 'function') res.set('Retry-After', '5'); + + return res.status(429).json(error.searchQueueTimeoutError()); + } + + if (searchDeadline.isSearchTimeoutError(err)) { + metrics.outcome = 'search_timeout'; + metrics.statusCode = 504; + metrics.timeoutSource = err?.name === 'TimeoutError' ? 'opensearch' : 'request_deadline'; + metrics.abortReason = metrics.timeoutSource; + return res.status(504).json(error.searchTimeoutError()); } + if (isOpenSearchUnavailableError(err)) { + metrics.outcome = 'opensearch_unavailable'; + metrics.statusCode = 503; + if (typeof res.set === 'function') res.set('Retry-After', '5'); + + return res.status(503).json(error.openSearchError()); + } + + metrics.outcome = 'upstream_error'; + metrics.statusCode = 502; return res.status(502).json(error.upstreamSearchError()); + } finally { + lifecycle.complete(); + metrics.totalElapsedMs = searchPerformance.elapsedMs(handlerStart); + searchPerformance.logSearchPerformance(metrics, searchOptions, responseOptions, postFilters); } } @@ -420,10 +499,11 @@ async function webSearchHandler(req, res, next) { body.searchMetadata.errors = [error.upstreamSearchError()]; } } catch (ex) { - let err = error.upstreamSearchError(); + const timedOut = searchDeadline.isSearchTimeoutError(ex); + const err = timedOut ? error.searchTimeoutError() : error.upstreamSearchError(); body.searchMetadata.errors = [err]; - return res.status(502).json(body); + return res.status(timedOut ? 504 : 502).json(body); } return res.status(status).json(body); diff --git a/controllers/searchController.test.js b/controllers/searchController.test.js index 852d993..2a116f5 100644 --- a/controllers/searchController.test.js +++ b/controllers/searchController.test.js @@ -1,14 +1,28 @@ +const { EventEmitter } = require('events'); + const mockApiSearch = jest.fn(); const mockBasicSearch = jest.fn(); const mockSearchRaw = jest.fn(); +const mockSearchOpenSearch = jest.fn((body, options) => { + const operation = options?.operation; + const search = operation === 'cpe-candidate' ? mockSearchRaw : mockApiSearch; + options?.onStart?.({ + activeCount: 1, + activeByOperation: { + [operation]: 1 + } + }); + + return Promise.resolve(search(body, options)).then((result) => result?.data ?? result); +}); const mockShouldLogSearchPerformance = jest.fn(); const mockGetPostFilterCacheTtlMs = jest.fn(() => 60000); const mockGetPostFilterCacheMaxEntries = jest.fn(() => 100); const mockGetPostFilterColdSearchConcurrency = jest.fn(() => 10); const mockGetPostFilterColdSearchMaxQueueDepth = jest.fn(() => 500); -const mockGetAdvancedSearchManager = jest.fn(() => ({ - apiSearch: mockApiSearch -})); +const mockGetPostFilterColdSearchMaxWaitMs = jest.fn(() => 10000); +const mockGetSearchRequestDeadlineMs = jest.fn(() => 30000); +const mockIsSearchProviderContractError = jest.fn((err) => err?.code === 'SEARCH_PROVIDER_CONTRACT'); const mockGetBasicSearchManager = jest.fn(() => ({ search: mockBasicSearch })); @@ -22,17 +36,20 @@ jest.mock('cve-core', () => { }); jest.mock('../utils/searchProvider', () => ({ - getAdvancedSearchManager: mockGetAdvancedSearchManager, getBasicSearchManager: mockGetBasicSearchManager, getPostFilterCacheMaxEntries: mockGetPostFilterCacheMaxEntries, getPostFilterCacheTtlMs: mockGetPostFilterCacheTtlMs, getPostFilterColdSearchConcurrency: mockGetPostFilterColdSearchConcurrency, getPostFilterColdSearchMaxQueueDepth: mockGetPostFilterColdSearchMaxQueueDepth, - searchRaw: mockSearchRaw, + getPostFilterColdSearchMaxWaitMs: mockGetPostFilterColdSearchMaxWaitMs, + getSearchRequestDeadlineMs: mockGetSearchRequestDeadlineMs, + isSearchProviderContractError: mockIsSearchProviderContractError, + searchOpenSearch: mockSearchOpenSearch, shouldLogSearchPerformance: mockShouldLogSearchPerformance })); const controller = require('./searchController.js'); +const searchInFlightRegistry = require('../utils/searchInFlightRegistry'); const searchPostFilterCache = require('../utils/searchPostFilterCache'); function createResponse() { @@ -45,6 +62,7 @@ function createResponse() { function advancedResponse(totalValue, hits = []) { return { + status: 'ok', data: { hits: { total: { @@ -86,6 +104,24 @@ function deferred() { return state; } +function captureTimers() { + const timers = []; + jest.spyOn(global, 'setTimeout').mockImplementation((callback, delay) => { + const timer = { + unref: jest.fn() + }; + timers.push({ + callback, + delay, + timer + }); + return timer; + }); + jest.spyOn(global, 'clearTimeout').mockImplementation(() => {}); + + return timers; +} + function cpeHit(id, cpeMatch) { return { _id: id, @@ -112,19 +148,25 @@ describe('Search controller SEARCH handler', () => { mockApiSearch.mockReset(); mockBasicSearch.mockReset(); mockSearchRaw.mockReset(); + mockSearchOpenSearch.mockClear(); mockShouldLogSearchPerformance.mockReset(); mockShouldLogSearchPerformance.mockReturnValue(false); - mockGetAdvancedSearchManager.mockClear(); mockGetBasicSearchManager.mockClear(); mockGetPostFilterCacheTtlMs.mockClear(); mockGetPostFilterCacheMaxEntries.mockClear(); mockGetPostFilterColdSearchConcurrency.mockClear(); mockGetPostFilterColdSearchMaxQueueDepth.mockClear(); + mockGetPostFilterColdSearchMaxWaitMs.mockClear(); + mockGetSearchRequestDeadlineMs.mockClear(); + mockIsSearchProviderContractError.mockClear(); mockGetPostFilterCacheTtlMs.mockReturnValue(60000); mockGetPostFilterCacheMaxEntries.mockReturnValue(100); mockGetPostFilterColdSearchConcurrency.mockReturnValue(10); mockGetPostFilterColdSearchMaxQueueDepth.mockReturnValue(500); + mockGetPostFilterColdSearchMaxWaitMs.mockReturnValue(10000); + mockGetSearchRequestDeadlineMs.mockReturnValue(30000); searchPostFilterCache.clear(); + searchInFlightRegistry.clear(); jest.spyOn(console, 'log').mockImplementation(() => {}); res = createResponse(); @@ -132,6 +174,7 @@ describe('Search controller SEARCH handler', () => { }); afterEach(() => { + searchInFlightRegistry.clear(); jest.restoreAllMocks(); }); @@ -156,48 +199,53 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockGetAdvancedSearchManager).toHaveBeenCalledTimes(1); - expect(mockApiSearch).toHaveBeenCalledWith( - null, { - filters: [ - { - terms: { - 'cveMetadata.cveId.keyword': ['CVE-2024-0001'] - } - } - ], - rangeObjects: [ - { - range: { - 'cveMetadata.datePublished': { - gte: '2024-01-01T00:00:00.000Z', - lte: '2024-01-31T00:00:00.000Z' + query: { + bool: { + must: [ + { + simple_query_string: { + query: 'remote* code*', + default_operator: 'AND', + fields: ['containers.cna.descriptions.value'] + } } - } - }, - { - range: { - 'cveMetadata.dateUpdated': { - gte: '2024-02-01T00:00:00.000Z', - lte: '2024-02-29T00:00:00.000Z' + ], + filter: [ + { + terms: { + 'cveMetadata.cveId.keyword': ['CVE-2024-0001'] + } + }, + { + range: { + 'cveMetadata.datePublished': { + gte: '2024-01-01T00:00:00.000Z', + lte: '2024-01-31T00:00:00.000Z' + } + } + }, + { + range: { + 'cveMetadata.dateUpdated': { + gte: '2024-02-01T00:00:00.000Z', + lte: '2024-02-29T00:00:00.000Z' + } + } } - } + ] } - ], - resultsPerPage: 10, + }, + track_total_hits: true, + size: 10, from: 20 }, - [ - { - simple_query_string: { - query: 'remote* code*', - default_operator: 'AND', - fields: ['containers.cna.descriptions.value'] - } - } - ] + expect.objectContaining({ + operation: 'standard', + signal: expect.any(AbortSignal), + onStart: expect.any(Function) + }) ); expect(res.status).toHaveBeenCalledWith(200); @@ -223,19 +271,16 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockApiSearch.mock.calls[0][1]).toStrictEqual({ - filters: [ + expect(mockApiSearch.mock.calls[0][0].query.bool).toStrictEqual({ + must: [], + filter: [ { match_phrase: { 'containers.cna.descriptions.value': 'remote code' } } - ], - rangeObjects: [], - resultsPerPage: 20, - from: 0 + ] }); - expect(mockApiSearch.mock.calls[0][2]).toStrictEqual([]); }); test('SEARCH builds an exact terms filter for cveIds', async () => { @@ -251,14 +296,14 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockApiSearch.mock.calls[0][1].filters).toStrictEqual([ + expect(mockApiSearch.mock.calls[0][0].query.bool.filter).toStrictEqual([ { terms: { 'cveMetadata.cveId.keyword': ['CVE-2024-0001', 'CVE-2024-0002'] } } ]); - expect(mockApiSearch.mock.calls[0][2]).toStrictEqual([]); + expect(mockApiSearch.mock.calls[0][0].query.bool.must).toStrictEqual([]); }); test('SEARCH builds rejected-state exclusions for noRejected', async () => { @@ -274,7 +319,7 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockApiSearch.mock.calls[0][1].filters).toStrictEqual([ + expect(mockApiSearch.mock.calls[0][0].query.bool.filter).toStrictEqual([ { bool: { must_not: [ @@ -296,7 +341,7 @@ describe('Search controller SEARCH handler', () => { } } ]); - expect(mockApiSearch.mock.calls[0][2]).toStrictEqual([]); + expect(mockApiSearch.mock.calls[0][0].query.bool.must).toStrictEqual([]); }); test('SEARCH processes virtualMatchString into a structured CPE criteria filter', async () => { @@ -315,6 +360,7 @@ describe('Search controller SEARCH handler', () => { expect(mockSearchRaw.mock.calls[0][0]._source).toStrictEqual([ 'cveMetadata.cveId', 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria', + 'containers.cna.cpeApplicability.nodes.cpeMatch.vulnerable', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionStartIncluding', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionStartExcluding', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionEndIncluding', @@ -335,7 +381,8 @@ describe('Search controller SEARCH handler', () => { ].map((value) => ({ prefix: { 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria.keyword': { - value + value, + case_insensitive: true } } })), @@ -385,7 +432,8 @@ describe('Search controller SEARCH handler', () => { ].map((value) => ({ prefix: { 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria.keyword': { - value + value, + case_insensitive: true } } })), @@ -393,8 +441,8 @@ describe('Search controller SEARCH handler', () => { } }); expect(filters[2]).toStrictEqual({ - match_phrase: { - 'containers.cna.cpeApplicability.nodes.cpeMatch.vulnerable': 'true' + term: { + 'containers.cna.cpeApplicability.nodes.cpeMatch.vulnerable': true } }); expect(body.size).toBe(10000); @@ -402,6 +450,34 @@ describe('Search controller SEARCH handler', () => { expect(mockApiSearch).not.toHaveBeenCalled(); }); + test('SEARCH requires the matching CPE entry itself to be vulnerable', async () => { + const matchingNotVulnerable = cpeHit('CVE-2024-0001', { + criteria: 'cpe:2.3:a:vendor:product:1.0:*:*:*:*:*:*:*', + vulnerable: false + }); + matchingNotVulnerable._source.containers.cna.cpeApplicability.nodes[0].cpeMatch.push({ + criteria: 'cpe:2.3:a:other:product:1.0:*:*:*:*:*:*:*', + vulnerable: true + }); + mockSearchRaw.mockResolvedValue(rawResponse(1, [matchingNotVulnerable])); + + await controller.SEARCH( + { + query: { + cpeName: 'cpe:2.3:a:vendor:product:1.0:*:*:*:*:*:*:*', + isVulnerable: true + } + }, + res, + next + ); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json.mock.calls[0][0].data.hits.total.value).toBe(0); + expect(res.json.mock.calls[0][0].data.hits.hits).toStrictEqual([]); + expect(mockApiSearch).not.toHaveBeenCalled(); + }); + test('SEARCH applies semantic CPE version filtering before building response metadata', async () => { const applicable = cpeHit('CVE-2024-0001', { criteria: 'cpe:2.3:a:vendor:product:*:*:*:*:*:*:*:*', @@ -442,20 +518,27 @@ describe('Search controller SEARCH handler', () => { ); expect(mockApiSearch).toHaveBeenCalledWith( - null, { - filters: [ - { - terms: { - 'cveMetadata.cveId.keyword': ['CVE-2024-0001'] - } + query: { + bool: { + must: [], + filter: [ + { + terms: { + 'cveMetadata.cveId.keyword': ['CVE-2024-0001'] + } + } + ] } - ], - rangeObjects: [], - resultsPerPage: 1, + }, + track_total_hits: true, + size: 1, from: 0 }, - [] + expect.objectContaining({ + operation: 'page-fetch', + signal: expect.any(AbortSignal) + }) ); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith({ @@ -473,6 +556,166 @@ describe('Search controller SEARCH handler', () => { }); }); + test('applySearchPostFilters does not combine platform and version matches from different CPE entries', () => { + const candidate = cpeHit('CVE-2026-0250', { + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:macos:*:*:*:*:*', + vulnerable: true, + versionStartIncluding: '6.0', + versionEndExcluding: '6.1' + }); + candidate._source.containers.cna.cpeApplicability.nodes[0].cpeMatch.push( + { + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:macos:*:*:*:*:*', + vulnerable: true, + versionStartIncluding: '6.2', + versionEndExcluding: '6.2.7' + }, + { + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:android:*:*:*:*:*', + vulnerable: true, + versionStartIncluding: '6.1', + versionEndExcluding: '6.1.13' + }, + { + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:chrome_os:*:*:*:*:*', + vulnerable: true, + versionStartIncluding: '6.1', + versionEndExcluding: '6.1.13' + } + ); + + const response = controller.applySearchPostFilters( + advancedResponse(1, [candidate]), + { + cpeName: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:6.1.1:*:macos:*:*:*:*:*', + isVulnerable: true + }, + { + from: 0, + resultsPerPage: 20 + } + ); + + expect(response.data.hits.total.value).toBe(0); + expect(response.data.hits.hits).toStrictEqual([]); + }); + + test('applySearchPostFilters matches CPE string literals without lexical case', () => { + const candidate = cpeHit('CVE-2026-0251', { + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:*:*:*:macOS:*:*', + vulnerable: true, + versionStartIncluding: '6.0', + versionEndExcluding: '6.1' + }); + + const response = controller.applySearchPostFilters( + advancedResponse(1, [candidate]), + { + cpeName: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:6.0.1:*:*:*:*:macos:*:*', + isVulnerable: true + }, + { + from: 0, + resultsPerPage: 20 + } + ); + + expect(response.data.hits.total.value).toBe(1); + expect(response.data.hits.hits).toStrictEqual([candidate]); + }); + + test('applySearchPostFilters preserves exact version padding while normalizing numeric range segments', () => { + const exactCandidate = cpeHit('CVE-2025-9392', { + criteria: 'cpe:2.3:o:linksys:re6250_firmware:1.0.04.001:*:*:*:*:*:*:*', + vulnerable: true + }); + const rangeCandidate = cpeHit('CVE-2026-33280', { + criteria: 'cpe:2.3:o:buffalo:wsr3600be4-kh_firmware:*:*:*:*:*:*:*:*', + vulnerable: true, + versionEndExcluding: '6.02' + }); + + const exactResponse = controller.applySearchPostFilters( + advancedResponse(1, [exactCandidate]), + { + cpeName: 'cpe:2.3:o:linksys:re6250_firmware:1.0.4.1:*:*:*:*:*:*:*', + isVulnerable: true + }, + { + from: 0, + resultsPerPage: 20 + } + ); + const includedRangeResponse = controller.applySearchPostFilters( + advancedResponse(1, [rangeCandidate]), + { + cpeName: 'cpe:2.3:o:buffalo:wsr3600be4-kh_firmware:6.1:*:*:*:*:*:*:*', + isVulnerable: true + }, + { + from: 0, + resultsPerPage: 20 + } + ); + const excludedRangeResponse = controller.applySearchPostFilters( + advancedResponse(1, [rangeCandidate]), + { + cpeName: 'cpe:2.3:o:buffalo:wsr3600be4-kh_firmware:6.2:*:*:*:*:*:*:*', + isVulnerable: true + }, + { + from: 0, + resultsPerPage: 20 + } + ); + + expect(exactResponse.data.hits.total.value).toBe(0); + expect(includedRangeResponse.data.hits.hits).toStrictEqual([rangeCandidate]); + expect(excludedRangeResponse.data.hits.total.value).toBe(0); + }); + + test('applySearchPostFilters preserves CPE NA matches across numeric version ranges', () => { + const criteria = 'cpe:2.3:a:microsoft:edge:*:*:*:*:*:android:*:*'; + const candidates = [ + cpeHit('CVE-2020-17153', { + criteria, + vulnerable: true, + versionStartIncluding: '-' + }), + cpeHit('CVE-2024-38208', { + criteria, + vulnerable: true, + versionStartIncluding: '1.0.0', + versionEndExcluding: '128.0.2739.42' + }), + cpeHit('CVE-2026-35429', { + criteria, + vulnerable: true, + versionStartIncluding: '1.0.0', + versionEndExcluding: '148.0.3967.55' + }) + ]; + + const response = controller.applySearchPostFilters( + advancedResponse(candidates.length, candidates), + { + cpeName: 'cpe:2.3:a:microsoft:edge:-:*:*:*:*:android:*:*', + isVulnerable: true + }, + { + from: 0, + resultsPerPage: 20 + } + ); + + expect(response.data.hits.total.value).toBe(3); + expect(response.data.hits.hits.map((hit) => hit._id)).toStrictEqual([ + 'CVE-2020-17153', + 'CVE-2024-38208', + 'CVE-2026-35429' + ]); + }); + test('SEARCH applies semantic virtualMatchString version range filtering before building response metadata', async () => { const applicable = cpeHit('CVE-2024-0001', { criteria: 'cpe:2.3:o:linux:linux_kernel:2.5:*:*:*:*:*:*:*' @@ -515,18 +758,24 @@ describe('Search controller SEARCH handler', () => { expect(mockSearchRaw.mock.calls[0][0].size).toBe(10000); expect(mockSearchRaw.mock.calls[0][0].from).toBe(0); - expect(mockApiSearch.mock.calls[0][1]).toStrictEqual({ - filters: [ - { - terms: { - 'cveMetadata.cveId.keyword': ['CVE-2024-0001'] - } + expect(mockApiSearch.mock.calls[0][0].query.bool.filter).toStrictEqual([ + { + terms: { + 'cveMetadata.cveId.keyword': ['CVE-2024-0001'] } - ], - rangeObjects: [], - resultsPerPage: 1, + } + ]); + expect(mockApiSearch.mock.calls[0][0]).toMatchObject({ + track_total_hits: true, + size: 1, from: 0 }); + expect(mockApiSearch.mock.calls[0][1]).toEqual( + expect.objectContaining({ + operation: 'page-fetch', + signal: expect.any(AbortSignal) + }) + ); expect(res.status).toHaveBeenCalledWith(200); expect(res.json).toHaveBeenCalledWith({ page: 1, @@ -580,16 +829,16 @@ describe('Search controller SEARCH handler', () => { expect(mockSearchRaw).toHaveBeenCalledTimes(1); expect(mockApiSearch).toHaveBeenCalledTimes(2); - expect(mockApiSearch.mock.calls[1][1]).toStrictEqual({ - filters: [ - { - terms: { - 'cveMetadata.cveId.keyword': ['CVE-2024-0001'] - } + expect(mockApiSearch.mock.calls[1][0].query.bool.filter).toStrictEqual([ + { + terms: { + 'cveMetadata.cveId.keyword': ['CVE-2024-0001'] } - ], - rangeObjects: [], - resultsPerPage: 1, + } + ]); + expect(mockApiSearch.mock.calls[1][0]).toMatchObject({ + track_total_hits: true, + size: 1, from: 0 }); expect(secondRes.status).toHaveBeenCalledWith(200); @@ -669,6 +918,56 @@ describe('Search controller SEARCH handler', () => { }); }); + test('SEARCH keeps shared CPE work active when one client disconnects', async () => { + const candidateSearch = deferred(); + const applicable = cpeHit('CVE-2024-0001', { + criteria: 'cpe:2.3:o:linux:linux_kernel:2.5:*:*:*:*:*:*:*' + }); + const fullApplicable = { + _id: 'CVE-2024-0001', + _source: { + cveMetadata: { + cveId: 'CVE-2024-0001' + } + } + }; + const firstReq = new EventEmitter(); + const secondReq = new EventEmitter(); + const query = { + virtualMatchString: 'cpe:2.3:o:linux:linux_kernel', + resultsPerPage: 1, + pageNumber: 1 + }; + firstReq.query = query; + secondReq.query = query; + const firstRes = createResponse(); + const secondRes = createResponse(); + let operationSignal; + + mockSearchRaw.mockImplementation((_body, { signal }) => { + operationSignal = signal; + return candidateSearch.promise; + }); + mockApiSearch.mockResolvedValue(advancedResponse(1, [fullApplicable])); + + const firstSearch = controller.SEARCH(firstReq, firstRes, next); + await Promise.resolve(); + const secondSearch = controller.SEARCH(secondReq, secondRes, next); + await Promise.resolve(); + + firstReq.emit('aborted'); + await firstSearch; + + expect(operationSignal.aborted).toBe(false); + candidateSearch.resolve(rawResponse(1, [applicable])); + await secondSearch; + + expect(mockSearchRaw).toHaveBeenCalledTimes(1); + expect(mockApiSearch).toHaveBeenCalledTimes(1); + expect(firstRes.status).not.toHaveBeenCalled(); + expect(secondRes.status).toHaveBeenCalledWith(200); + }); + test('SEARCH applies semantic virtualMatchString filtering before building response metadata', async () => { const applicable = cpeHit('CVE-2024-0001', { criteria: 'cpe:2.3:o:microsoft:windows_10_1809:-:*:*:*:*:*:x86:*' @@ -747,6 +1046,7 @@ describe('Search controller SEARCH handler', () => { await controller.SEARCH( { + requestId: 'queue-full-request', query: { virtualMatchString: 'cpe:2.3:o:microsoft:windows_10_1809', resultsPerPage: '1', @@ -763,6 +1063,82 @@ describe('Search controller SEARCH handler', () => { error: 'SEARCH_QUEUE_FULL', message: 'Search service is busy. Please retry later.' }); + expect(JSON.parse(console.log.mock.calls[0][0])).toMatchObject({ + event: 'search.performance', + requestId: 'queue-full-request', + outcome: 'queue_full', + statusCode: 429, + strategy: 'cpe-candidate', + cacheStatus: 'miss', + coldSearchQueueState: { + activeCount: 1, + queuedCount: 0, + maxConcurrent: 1, + maxQueueDepth: 0, + maxWaitMs: 10000 + } + }); + + candidateSearch.resolve(rawResponse(0, [])); + await firstSearch; + }); + + test('SEARCH returns 429 when a cold semantic post-filter search exceeds the queue wait deadline', async () => { + const timers = captureTimers(); + const candidateSearch = deferred(); + const firstRes = createResponse(); + const secondRes = createResponse(); + + mockGetPostFilterColdSearchConcurrency.mockReturnValue(1); + mockGetPostFilterColdSearchMaxQueueDepth.mockReturnValue(1); + mockGetPostFilterColdSearchMaxWaitMs.mockReturnValue(10000); + mockSearchRaw.mockReturnValue(candidateSearch.promise); + + const firstSearch = controller.SEARCH( + { + query: { + virtualMatchString: 'cpe:2.3:o:linux:linux_kernel', + resultsPerPage: '1', + pageNumber: '1' + } + }, + firstRes, + next + ); + await Promise.resolve(); + + const secondSearch = controller.SEARCH( + { + requestId: 'queue-timeout-request', + query: { + virtualMatchString: 'cpe:2.3:o:microsoft:windows_10_1809', + resultsPerPage: '1', + pageNumber: '1' + } + }, + secondRes, + next + ); + await Promise.resolve(); + + timers.find(({ delay }) => delay === 10000).callback(); + await secondSearch; + + expect(secondRes.set).toHaveBeenCalledWith('Retry-After', '5'); + expect(secondRes.status).toHaveBeenCalledWith(429); + expect(secondRes.json).toHaveBeenCalledWith({ + error: 'SEARCH_QUEUE_TIMEOUT', + message: 'Search service is busy. Please retry later.' + }); + expect(JSON.parse(console.log.mock.calls[0][0])).toMatchObject({ + event: 'search.performance', + requestId: 'queue-timeout-request', + outcome: 'queue_timeout', + statusCode: 429, + strategy: 'cpe-candidate', + cacheStatus: 'miss', + coldSearchQueueWaitElapsedMs: expect.any(Number) + }); candidateSearch.resolve(rawResponse(0, [])); await firstSearch; @@ -797,6 +1173,7 @@ describe('Search controller SEARCH handler', () => { await controller.SEARCH( { + requestId: 'request-123', query: { keywordSearch: 'remote code', resultsPerPage: '1' @@ -811,10 +1188,16 @@ describe('Search controller SEARCH handler', () => { expect(timingLog).toMatchObject({ event: 'search.performance', route: '/search', + requestId: 'request-123', + outcome: 'success', + statusCode: 200, strategy: 'standard', + cacheStatus: 'not_applicable', optimizedFallback: false, cacheHit: false, inFlightHit: false, + openSearchOperations: ['standard'], + maxOpenSearchActiveRequests: 1, postFilters: [], requestedFrom: 0, requestedSize: 1, @@ -837,7 +1220,7 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockApiSearch.mock.calls[0][1].filters).toStrictEqual([ + expect(mockApiSearch.mock.calls[0][0].query.bool.filter).toStrictEqual([ { term: { 'containers.cna.metrics.cvssV3_0.baseSeverity.keyword': 'HIGH' @@ -864,7 +1247,7 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockApiSearch.mock.calls[0][1].filters).toStrictEqual([ + expect(mockApiSearch.mock.calls[0][0].query.bool.filter).toStrictEqual([ { bool: { filter: [ @@ -909,7 +1292,7 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockApiSearch.mock.calls[0][1].filters).toStrictEqual([ + expect(mockApiSearch.mock.calls[0][0].query.bool.filter).toStrictEqual([ { bool: { filter: [ @@ -966,7 +1349,7 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockApiSearch.mock.calls[0][2]).toStrictEqual([ + expect(mockApiSearch.mock.calls[0][0].query.bool.must).toStrictEqual([ { simple_query_string: { query: 'c\\+\\+* path\\:\\/tmp* \\(test\\)*', @@ -990,7 +1373,7 @@ describe('Search controller SEARCH handler', () => { next ); - expect(mockApiSearch.mock.calls[0][2]).toStrictEqual([ + expect(mockApiSearch.mock.calls[0][0].query.bool.must).toStrictEqual([ { simple_query_string: { query: '', @@ -1029,7 +1412,8 @@ describe('Search controller SEARCH handler', () => { ].map((value) => ({ prefix: { 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria.keyword': { - value + value, + case_insensitive: true } } })), @@ -1057,7 +1441,8 @@ describe('Search controller SEARCH handler', () => { { regexp: { 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria.keyword': { - value: 'cpe:2\\.3:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*' + value: 'cpe:2\\.3:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*', + case_insensitive: true } } } @@ -1086,25 +1471,179 @@ describe('Search controller SEARCH handler', () => { }); }); - test('SEARCH returns openSearchError when cve-core throws a ConnectionError', async () => { - mockApiSearch.mockRejectedValue({ - name: 'ConnectionError' + test('SEARCH calculates an out-of-range page from the requested page size', async () => { + const resp = advancedResponse(5); + mockApiSearch.mockResolvedValue(resp); + + await controller.SEARCH( + { + query: { + resultsPerPage: 10, + pageNumber: 2 + } + }, + res, + next + ); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ + page: 2, + pageSize: 5, + totalPages: 1, + ...resp }); + }); + + test.each([ + ['ConnectionError', { name: 'ConnectionError' }], + ['NoLivingConnectionsError', { name: 'NoLivingConnectionsError' }], + ['cve-core provider contract error', { code: 'SEARCH_PROVIDER_CONTRACT' }], + ['OpenSearch 503 response', { name: 'ResponseError', meta: { statusCode: 503 } }] + ])('SEARCH returns a retryable 503 when OpenSearch is unavailable through %s', async (_caseName, thrownError) => { + mockApiSearch.mockRejectedValue(thrownError); await controller.SEARCH( { + requestId: 'connection-error-request', query: {} }, res, next ); - expect(res.status).toHaveBeenCalledWith(400); + expect(res.status).toHaveBeenCalledWith(503); + expect(res.set).toHaveBeenCalledWith('Retry-After', '5'); expect(res.json).toHaveBeenCalledWith({ error: 'OPEN_SEARCH_ERROR', message: 'OpenSearch refused connection, please check that OpenSearch is running and accessible then try again.' }); + expect(JSON.parse(console.log.mock.calls[0][0])).toMatchObject({ + event: 'search.performance', + requestId: 'connection-error-request', + outcome: 'opensearch_unavailable', + statusCode: 503 + }); + }); + + test('SEARCH returns a sanitized 504 when OpenSearch times out', async () => { + mockApiSearch.mockRejectedValue({ + name: 'TimeoutError', + message: 'sensitive upstream timeout details' + }); + + await controller.SEARCH( + { + requestId: 'timeout-request', + query: {} + }, + res, + next + ); + + expect(res.status).toHaveBeenCalledWith(504); + expect(res.json).toHaveBeenCalledWith({ + error: 'SEARCH_TIMEOUT', + message: 'Search request timed out. Please try again later.' + }); + expect(JSON.parse(console.log.mock.calls[0][0])).toMatchObject({ + event: 'search.performance', + requestId: 'timeout-request', + outcome: 'search_timeout', + statusCode: 504, + timeoutSource: 'opensearch' + }); + }); + + test('SEARCH returns a sanitized 504 when the overall request deadline is exceeded', async () => { + const timers = captureTimers(); + mockApiSearch.mockImplementation( + (_body, { signal }) => + new Promise((resolve, reject) => { + signal.addEventListener('abort', () => reject(signal.reason), { once: true }); + }) + ); + + const search = controller.SEARCH( + { + requestId: 'request-deadline-request', + query: {} + }, + res, + next + ); + await Promise.resolve(); + + timers.find(({ delay }) => delay === 30000).callback(); + await search; + + expect(mockGetSearchRequestDeadlineMs).toHaveBeenCalledTimes(1); + expect(res.status).toHaveBeenCalledWith(504); + expect(res.json).toHaveBeenCalledWith({ + error: 'SEARCH_TIMEOUT', + message: 'Search request timed out. Please try again later.' + }); + expect(JSON.parse(console.log.mock.calls[0][0])).toMatchObject({ + event: 'search.performance', + requestId: 'request-deadline-request', + outcome: 'search_timeout', + statusCode: 504, + timeoutSource: 'request_deadline', + abortReason: 'request_deadline' + }); + }); + + test('SEARCH stops without writing a response when the client disconnects', async () => { + const req = new EventEmitter(); + req.requestId = 'client-disconnect-request'; + req.query = {}; + mockApiSearch.mockImplementation( + (_body, { signal }) => + new Promise((resolve, reject) => { + signal.addEventListener('abort', () => reject(signal.reason), { once: true }); + }) + ); + + const search = controller.SEARCH(req, res, next); + await Promise.resolve(); + req.emit('aborted'); + await search; + + expect(res.status).not.toHaveBeenCalled(); + expect(res.json).not.toHaveBeenCalled(); + expect(JSON.parse(console.log.mock.calls[0][0])).toMatchObject({ + event: 'search.performance', + requestId: 'client-disconnect-request', + outcome: 'client_disconnected', + statusCode: 499, + abortReason: 'client_disconnect', + strategy: 'standard' + }); + }); + + test('SEARCH does not start a page fetch after a client disconnects from a CPE search', async () => { + const req = new EventEmitter(); + req.requestId = 'cpe-client-disconnect-request'; + req.query = { + virtualMatchString: 'cpe:2.3:o:linux:linux_kernel', + resultsPerPage: 1, + pageNumber: 1 + }; + mockSearchRaw.mockImplementation(() => { + req.emit('aborted'); + return rawResponse(1, [ + cpeHit('CVE-2024-0001', { + criteria: 'cpe:2.3:o:linux:linux_kernel:2.5:*:*:*:*:*:*:*' + }) + ]); + }); + + await controller.SEARCH(req, res, next); + + expect(mockApiSearch).not.toHaveBeenCalled(); + expect(res.status).not.toHaveBeenCalled(); + expect(res.json).not.toHaveBeenCalled(); }); test('SEARCH returns sanitized errors from cve-core', async () => { @@ -1120,6 +1659,7 @@ describe('Search controller SEARCH handler', () => { await controller.SEARCH( { + requestId: 'upstream-error-request', query: {} }, res, @@ -1131,6 +1671,12 @@ describe('Search controller SEARCH handler', () => { error: 'UPSTREAM_SEARCH_ERROR', message: 'Search backend returned an error. Please try again later.' }); + expect(JSON.parse(console.log.mock.calls[0][0])).toMatchObject({ + event: 'search.performance', + requestId: 'upstream-error-request', + outcome: 'upstream_error', + statusCode: 502 + }); }); }); @@ -1141,11 +1687,12 @@ describe('Search controller WEB_SEARCH handler', () => { mockApiSearch.mockReset(); mockBasicSearch.mockReset(); mockSearchRaw.mockReset(); + mockSearchOpenSearch.mockClear(); mockShouldLogSearchPerformance.mockReset(); mockShouldLogSearchPerformance.mockReturnValue(false); - mockGetAdvancedSearchManager.mockClear(); mockGetBasicSearchManager.mockClear(); searchPostFilterCache.clear(); + searchInFlightRegistry.clear(); jest.spyOn(console, 'log').mockImplementation(() => {}); res = createResponse(); @@ -1153,6 +1700,7 @@ describe('Search controller WEB_SEARCH handler', () => { }); afterEach(() => { + searchInFlightRegistry.clear(); jest.restoreAllMocks(); }); @@ -1393,4 +1941,40 @@ describe('Search controller WEB_SEARCH handler', () => { }); expect(console.log).not.toHaveBeenCalled(); }); + + test('WEB_SEARCH returns a sanitized 504 when OpenSearch times out', async () => { + mockBasicSearch.mockRejectedValue({ + name: 'TimeoutError', + message: 'sensitive upstream timeout details' + }); + + await controller.WEB_SEARCH( + { + body: { + query: 'apache' + } + }, + res, + next + ); + + expect(res.status).toHaveBeenCalledWith(504); + expect(res.json.mock.calls[0][0]).toStrictEqual({ + searchMetadata: { + searchStatus: 'errors', + version: '2025-08-26', + cveCore: 'test-core-version', + cveMitreCore: 'test-core-version', + notes: [], + errors: [ + { + error: 'SEARCH_TIMEOUT', + message: 'Search request timed out. Please try again later.' + } + ] + }, + resultsTotal: 0, + data: [] + }); + }); }); diff --git a/package-lock.json b/package-lock.json index 38feb69..61f936e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,26 +1,26 @@ { "name": "cve-search-api", - "version": "0.3.0-beta", + "version": "0.4.0-beta", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cve-search-api", - "version": "0.3.0-beta", + "version": "0.4.0-beta", "dependencies": { "cors": "^2.8.5", "cve-core": "github:CVEProject/cve-core.git#dev-package-test", "debug": "~3.2.7", "dotenv": "^6.1.0", "express": "^4.22.2", + "express-rate-limit": "^8.6.1", "express-validator": "^7.2.1", "http-errors": "~1.8.1", - "morgan": "~1.10.0", + "morgan": "~1.11.0", "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@eslint/js": "^10.0.1", - "artillery": "^2.0.33", "eslint": "^10.6.0", "globals": "^17.7.0", "jest": "^28.1.3", @@ -29,37 +29,70 @@ "swagger-autogen": "^2.23.7" } }, - "node_modules/@artilleryio/int-commons": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/@artilleryio/int-commons/-/int-commons-2.24.0.tgz", - "integrity": "sha512-edCjaTcUQNWxGS7XD128zYV4wABnh4itGmK1bzRTUe78huYGN9d21/Sf/aHWgl/DbbufYvvvU1FX9PD1l0m7jg==", + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, - "license": "MPL-2.0", + "license": "MIT", "dependencies": { - "async": "^2.6.4", - "cheerio": "^1.2.0", - "debug": "^4.4.3", - "deep-for-each": "^3.0.0", - "espree": "^10.3.0", - "jsonpath-plus": "^10.3.0", - "lodash": "^4.18.0", - "ms": "^2.1.3" + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@artilleryio/int-commons/node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=0.4.0" + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@artilleryio/int-commons/node_modules/debug": { + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", @@ -77,4910 +110,1125 @@ } } }, - "node_modules/@artilleryio/int-commons/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@artilleryio/int-commons/node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@artilleryio/int-core": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/@artilleryio/int-core/-/int-core-2.28.0.tgz", - "integrity": "sha512-l1T2LMc0+BpGRH1sSfwb+jfeJWyTq0xrtJggRm3M7pMNU8Il1xZZBxxM2j+o7i8tXQPHZ/uSxdMUGNRaS6ygPA==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "@artilleryio/int-commons": "2.24.0", - "@artilleryio/sketches-js": "^2.1.1", - "agentkeepalive": "^4.6.0", - "arrivals": "^2.1.2", - "async": "^2.6.4", - "chalk": "^2.4.2", - "cheerio": "^1.2.0", - "cookie-parser": "^1.4.7", - "csv-parse": "^4.16.3", - "debug": "^4.4.3", - "decompress-response": "^6.0.0", - "deep-for-each": "^3.0.0", - "driftless": "^2.0.3", - "esprima": "^4.0.1", - "eventemitter3": "^5.0.4", - "fast-deep-equal": "^3.1.3", - "filtrex": "^0.5.4", - "form-data": "^4.0.5", - "got": "^14.6.6", - "hpagent": "^0.1.1", - "https-proxy-agent": "^5.0.0", - "lodash": "^4.18.0", - "ms": "^2.1.3", - "protobufjs": "^7.5.5", - "socket.io-client": "^4.8.3", - "socketio-wildcard": "^2.0.0", - "tough-cookie": "^5.1.2", - "uuid": "^11.1.0", - "ws": "^7.5.7" + "node": ">=6.9.0" } }, - "node_modules/@artilleryio/int-core/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, - "node_modules/@artilleryio/int-core/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, - "node_modules/@artilleryio/int-core/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@artilleryio/int-core/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@artilleryio/int-core/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { - "node": ">=6.0" + "node": ">=6.9.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@artilleryio/int-core/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=6.9.0" } }, - "node_modules/@artilleryio/int-core/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==", + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, - "node_modules/@artilleryio/int-core/node_modules/hpagent": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", - "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@artilleryio/int-core/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==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, "engines": { - "node": ">=4" + "node": ">=6.9.0" } }, - "node_modules/@artilleryio/sketches-js": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@artilleryio/sketches-js/-/sketches-js-2.1.1.tgz", - "integrity": "sha512-H3D50vDb37E3NGYXY0eUFAm5++moElaqoAu0MWYZhgzaA3IT2E67bRCL8U4LKHuVf/MgDZk14uawIjc4WVjOUQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@aws-sdk/checksums": { - "version": "3.1000.16", - "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.16.tgz", - "integrity": "sha512-EKnvkXSmz3IpA99tCNuI+dLFXyZyClSm8zns9sB/elvkU+MTuomAs6toJMPMBf98/fICG/urXDkzGz0/c3yyAQ==", + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/checksums/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-cloudwatch": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch/-/client-cloudwatch-3.1083.0.tgz", - "integrity": "sha512-k9n4XAYtl0QhQLQA9SGEl3hpqvTmlrAbPP44xJ4fzeZrYtRdRuvq5jTo8IcbId0VrPsBPB04DU6uJjulfgoQSA==", + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/middleware-compression": "^4.5.7", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-cloudwatch-logs": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.1083.0.tgz", - "integrity": "sha512-rTfxzi7nhd7FXrdASPhenE0aZmrFMY8D1c8CUyE/L+FlfWrCO/e5LbFrxnUvrteY1KM+VUVWzZHjrmfHQW3upw==", + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.0.0" } }, - "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-cloudwatch/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-ec2": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ec2/-/client-ec2-3.1083.0.tgz", - "integrity": "sha512-An9zV+dlsfwAcVOLwQWlfDHaH4eS8vWZqANn/sAshPqWZOs4a+wV4IMI+nezU2gBU65vDTJdwvSwN7OMT5RrXA==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/middleware-sdk-ec2": "^3.972.45", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-ecs": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.1083.0.tgz", - "integrity": "sha512-BPEDMvMX58D46nc1aDehZ+zUzQ30/yxgDU0lA6N2lEgBYBYnZqA1PHmi/2LBcyGouf5cNL2ldmuNosWvjl/hDg==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-ecs/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-iam": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.1083.0.tgz", - "integrity": "sha512-1BfmzDtaHTt4zQqbeV134bly5xN0j6TaPBisb9K1iB7Arq2LdjD8sgEWxmM0qZeO/9B0enpngaWXHaF74gnPTQ==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.12.13" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-iam/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-lambda": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.1083.0.tgz", - "integrity": "sha512-k6O+nOkNFy3LPpZ/GFD/admJMAhzdpxbZ6swdXoWjRltYAFVhXFdI6pdIWfai0gVA+xZ7MKM9sRTiPLBASnYiQ==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-lambda/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1083.0.tgz", - "integrity": "sha512-3YOicHy6qjexsy4rHk5jPvtLgg6Ho1u+ycOWtJcVTAIMlMq32MRtnhllAyFSezhAXeBnnLLtKtAGXim7vV8oog==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/checksums": "^3.1000.16", - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/middleware-sdk-s3": "^3.972.62", - "@aws-sdk/signature-v4-multi-region": "^3.996.39", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-s3/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-sqs": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sqs/-/client-sqs-3.1083.0.tgz", - "integrity": "sha512-onW6oteUiZbVpGn2oMsPVk9bNKLLoYkIjzbmnNvIjPGvOTt5EWCW3huPWDfuKUiM69NkExWGqn0obmxfh52yqw==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/middleware-sdk-sqs": "^3.972.35", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-sqs/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-ssm": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.1083.0.tgz", - "integrity": "sha512-1hfvo8G16GX6zk1JkzWIB9kFuoC/a9rOuucbxi1LgFuGa2gHLt8tAYmkLLlpBVMRLCZWcYR9FihyYA3jATMtEQ==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.1083.0.tgz", - "integrity": "sha512-3DRm8UNj3aByu0U5Sgvs5Ut3V1XbWiST/JWF3/Zk57kFeVYm4a/EjQH4k9jTI8pk4dmYkZZJMcaecqTRRYzYZg==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/signature-v4-multi-region": "^3.996.39", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/core": { - "version": "3.975.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz", - "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@aws-sdk/xml-builder": "^3.972.34", - "@aws/lambda-invoke-store": "^0.3.0", - "@smithy/core": "^3.29.2", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/core/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.972.56", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.56.tgz", - "integrity": "sha512-nedaZz6Wz2FGUBMsWhlvBPGIkTMfRuMJ99YDHJI4CaC31JS8WPyTavEAio74cfd5nGhi0A8XASB4fZA5VqTdOQ==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.57", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.57.tgz", - "integrity": "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.59", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz", - "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.1.tgz", - "integrity": "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-env": "^3.972.57", - "@aws-sdk/credential-provider-http": "^3.972.59", - "@aws-sdk/credential-provider-login": "^3.972.63", - "@aws-sdk/credential-provider-process": "^3.972.57", - "@aws-sdk/credential-provider-sso": "^3.973.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.63", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/credential-provider-imds": "^4.4.7", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.63", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.63.tgz", - "integrity": "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.66", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.66.tgz", - "integrity": "sha512-f+qjRXZpz7sgzbc4QB+6nLKfyKFgRRXzWdXbsKPv/VhVRyHsDyq4yBWC/B75BAJpFIcUeI2XR/3gdWJ677zB4A==", + "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": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.57", - "@aws-sdk/credential-provider-http": "^3.972.59", - "@aws-sdk/credential-provider-ini": "^3.973.1", - "@aws-sdk/credential-provider-process": "^3.972.57", - "@aws-sdk/credential-provider-sso": "^3.973.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.63", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/credential-provider-imds": "^4.4.7", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.57", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.57.tgz", - "integrity": "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==", + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.1.tgz", - "integrity": "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==", + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/token-providers": "3.1083.0", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.63", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.63.tgz", - "integrity": "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==", + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1083.0.tgz", - "integrity": "sha512-i2q3Jgt365lZp7BSDqDSf283WvISrXob1zsql093LK3G2svYRHRvcNv995SsKtAzRENwHem2TC2vWeghrgBkxg==", + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/credential-provider-cognito-identity": "^3.972.56", - "@aws-sdk/credential-provider-env": "^3.972.57", - "@aws-sdk/credential-provider-http": "^3.972.59", - "@aws-sdk/credential-provider-ini": "^3.973.1", - "@aws-sdk/credential-provider-login": "^3.972.63", - "@aws-sdk/credential-provider-node": "^3.972.66", - "@aws-sdk/credential-provider-process": "^3.972.57", - "@aws-sdk/credential-provider-sso": "^3.973.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.63", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/credential-provider-imds": "^4.4.7", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@aws-sdk/credential-providers/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/middleware-sdk-ec2": { - "version": "3.972.45", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-ec2/-/middleware-sdk-ec2-3.972.45.tgz", - "integrity": "sha512-wcSIL3HWcEQQkf+eKbyhwqSML23uTUI2zAYexRRa0akL74pnq5olKh06a0kGHWsn6SUHcw9zy9dExA8UuJAUCw==", + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/middleware-sdk-ec2/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "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": "0BSD" + "license": "MIT" }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.62", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.62.tgz", - "integrity": "sha512-k8JJwYXVYlOOjWnPZDThQS1xDFJgi5Dokt73qFlDtrZAbdcint5aIdjB9XgJAAQVP5OoqcefQmh1FYXiPpvsvw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/signature-v4-multi-region": "^3.996.39", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "node_modules/@commander-js/extra-typings": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-10.0.3.tgz", + "integrity": "sha512-OIw28QV/GlP8k0B5CJTRsl8IyNvd0R8C8rfo54Yz9P388vCNDgdNrFlKxZTGqps+5j6lSw3Ss9JTQwcur1w1oA==", + "license": "MIT", + "peerDependencies": { + "commander": "10.0.x" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/middleware-sdk-sqs": { - "version": "3.972.35", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.35.tgz", - "integrity": "sha512-J1u7OABwjjBB7sUJiET05dPVAfjFeR6vfM5//DInKuPzc7PhPVNEviC7RqbsIsBdZi8xICVnHemFrTuvGmhySA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=20.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-sqs/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.31", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.31.tgz", - "integrity": "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/signature-v4-multi-region": "^3.996.39", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/fetch-http-handler": "^5.6.4", - "@smithy/node-http-handler": "^4.9.4", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=20.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, - "license": "0BSD" + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.39", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.39.tgz", - "integrity": "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==", + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.974.0", - "@smithy/signature-v4": "^5.6.3", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, "engines": { - "node": ">=20.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.1083.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1083.0.tgz", - "integrity": "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==", + "node_modules/@eslint/config-array/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.975.1", - "@aws-sdk/nested-clients": "^3.997.31", - "@aws-sdk/types": "^3.974.0", - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "ms": "^2.1.3" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@aws-sdk/token-providers/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/types": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz", - "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==", + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@eslint/core": "^1.2.1" }, "engines": { - "node": ">=20.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@aws-sdk/types/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.34", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz", - "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==", + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=20.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@aws-sdk/xml-builder/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", - "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/@azure/abort-controller": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", - "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.2.0" - }, + "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@azure/arm-containerinstance": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@azure/arm-containerinstance/-/arm-containerinstance-9.1.0.tgz", - "integrity": "sha512-N9T3/HJwWXvJuz7tin+nO+DYYCTGHILJ5Die3TtdF8Wd1ITfXGqB0vY/wOnspUu/AGojhaIKGmawAfPdw2kX8w==", + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-auth": "^1.3.0", - "@azure/core-client": "^1.7.0", - "@azure/core-lro": "^2.5.0", - "@azure/core-paging": "^1.2.0", - "@azure/core-rest-pipeline": "^1.8.0", - "tslib": "^2.2.0" + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=14.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@azure/core-auth": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", - "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-util": "^1.13.0", - "tslib": "^2.6.2" + "@humanfs/types": "^0.15.0" }, "engines": { - "node": ">=20.0.0" + "node": ">=18.18.0" } }, - "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.6.2" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=18.18.0" } }, - "node_modules/@azure/core-auth/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/core-client": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.2.tgz", - "integrity": "sha512-1D2LpsU7y9xrqKjdIbsB7PlrRePw0xsVV8p+AKTlzITrWmscajryfJCdDJB/oGwvDI5HmRo04eMMADB67uwAwQ==", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.10.0", - "@azure/core-rest-pipeline": "^1.22.0", - "@azure/core-tracing": "^1.3.0", - "@azure/core-util": "^1.13.0", - "@azure/logger": "^1.3.0", - "tslib": "^2.6.2" - }, + "license": "Apache-2.0", "engines": { - "node": ">=20.0.0" + "node": ">=18.18.0" } }, - "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" - }, + "license": "Apache-2.0", "engines": { - "node": ">=18.0.0" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@azure/core-client/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/core-http-compat": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.4.0.tgz", - "integrity": "sha512-f1P96IB399YiN2ARYHP7EpZi3Bf3wH4SN2lGzrw7JVwm7bbsVYtf2iKSBwTywD2P62NOPZGHFSZi+6jjb75JuA==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@azure/abort-controller": "^2.1.2" - }, + "license": "Apache-2.0", "engines": { - "node": ">=20.0.0" + "node": ">=18.18" }, - "peerDependencies": { - "@azure/core-client": "^1.10.0", - "@azure/core-rest-pipeline": "^1.22.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@azure/core-http-compat/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@azure/core-http-compat/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/core-lro": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", - "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "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": "MIT", + "license": "ISC", "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-util": "^1.2.0", - "@azure/logger": "^1.0.0", - "tslib": "^2.6.2" + "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": ">=18.0.0" + "node": ">=8" } }, - "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "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", - "dependencies": { - "tslib": "^2.6.2" - }, "engines": { - "node": ">=18.0.0" + "node": ">=8" } }, - "node_modules/@azure/core-lro/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/core-paging": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", - "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "node_modules/@jest/console": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", + "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@jest/types": "^28.1.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "slash": "^3.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/core-paging/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/core-rest-pipeline": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.24.0.tgz", - "integrity": "sha512-PpLsoDQ3AMmKZ0VU+0GrmqMxgp/sExjlVm4R+nLWngeoEGAzOIPVifaxKGU5gMv+nWELUoHfvrolWD+ZS/nFJg==", + "node_modules/@jest/core": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", + "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", "dev": true, "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.10.0", - "@azure/core-tracing": "^1.3.0", - "@azure/core-util": "^1.13.0", - "@azure/logger": "^1.3.0", - "@typespec/ts-http-runtime": "^0.3.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.6.2" + "@jest/console": "^28.1.3", + "@jest/reporters": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.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": "^28.1.3", + "jest-config": "^28.1.3", + "jest-haste-map": "^28.1.3", + "jest-message-util": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-resolve": "^28.1.3", + "jest-resolve-dependencies": "^28.1.3", + "jest-runner": "^28.1.3", + "jest-runtime": "^28.1.3", + "jest-snapshot": "^28.1.3", + "jest-util": "^28.1.3", + "jest-validate": "^28.1.3", + "jest-watcher": "^28.1.3", + "micromatch": "^4.0.4", + "pretty-format": "^28.1.3", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@azure/core-rest-pipeline/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/core-tracing": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", - "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "node_modules/@jest/environment": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", + "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@jest/fake-timers": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/node": "*", + "jest-mock": "^28.1.3" }, "engines": { - "node": ">=20.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/core-tracing/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/core-util": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", - "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "node_modules/@jest/expect": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", + "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", "dev": true, "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@typespec/ts-http-runtime": "^0.3.0", - "tslib": "^2.6.2" + "expect": "^28.1.3", + "jest-snapshot": "^28.1.3" }, "engines": { - "node": ">=20.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "node_modules/@jest/expect-utils": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", + "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "jest-get-type": "^28.0.2" }, "engines": { - "node": ">=18.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/core-util/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/core-xml": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.5.1.tgz", - "integrity": "sha512-xcNRHqCoSp4AunOALEae6A8f3qATb83gSrm31Iqb01OzblvC3/W/bfXozcq78EzIdzZzuH1bZ2NvRR0TdX709w==", + "node_modules/@jest/fake-timers": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", + "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", "dev": true, "license": "MIT", "dependencies": { - "fast-xml-parser": "^5.5.9", - "tslib": "^2.8.1" + "@jest/types": "^28.1.3", + "@sinonjs/fake-timers": "^9.1.2", + "@types/node": "*", + "jest-message-util": "^28.1.3", + "jest-mock": "^28.1.3", + "jest-util": "^28.1.3" }, "engines": { - "node": ">=20.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/core-xml/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/identity": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.1.tgz", - "integrity": "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw==", + "node_modules/@jest/globals": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", + "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", "dev": true, "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.9.0", - "@azure/core-client": "^1.9.2", - "@azure/core-rest-pipeline": "^1.17.0", - "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.11.0", - "@azure/logger": "^1.0.0", - "@azure/msal-browser": "^5.5.0", - "@azure/msal-node": "^5.1.0", - "open": "^10.1.0", - "tslib": "^2.2.0" + "@jest/environment": "^28.1.3", + "@jest/expect": "^28.1.3", + "@jest/types": "^28.1.3" }, "engines": { - "node": ">=20.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/identity/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "node_modules/@jest/reporters": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", + "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^28.1.3", + "@jest/test-result": "^28.1.3", + "@jest/transform": "^28.1.3", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "@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": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.3", + "jest-util": "^28.1.3", + "jest-worker": "^28.1.3", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": ">=18.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@azure/identity/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/logger": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", - "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@typespec/ts-http-runtime": "^0.3.0", - "tslib": "^2.6.2" + "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": ">=20.0.0" + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@azure/logger/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/msal-browser": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-5.17.0.tgz", - "integrity": "sha512-/yTnW2TCk9Mh+2b/NOaHAN+MryUNxzRTaJD/YtrqOA9bpBWfTXn/iyReRbaLrK/btBo3stEzLyEvuWp2NZ5DuA==", + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "@azure/msal-common": "16.11.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=0.8.0" + "node": "*" } }, - "node_modules/@azure/msal-common": { - "version": "16.11.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-16.11.1.tgz", - "integrity": "sha512-yPohvMwWLv1XnaWnIUyKUh8CvcVChCGqG/VluGwfGmaAfrZTNt5yQ+sIs462Sgw6+e2K83KGmMJ860p73ZSCrw==", + "node_modules/@jest/schemas": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", + "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", "dev": true, "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.24.1" + }, "engines": { - "node": ">=0.8.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/msal-node": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-5.4.0.tgz", - "integrity": "sha512-6EZEParwHRlnSSIikw8FNAnAzwmh71uhveUXdPNFeZFviJ9SH+rwFiurhjzXqICYTrpm3E+dj693QOwfPbJXAQ==", + "node_modules/@jest/source-map": { + "version": "28.1.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", + "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", "dev": true, "license": "MIT", "dependencies": { - "@azure/msal-common": "16.11.1", - "jsonwebtoken": "^9.0.0" + "@jridgewell/trace-mapping": "^0.3.13", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" }, "engines": { - "node": ">=20" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/storage-blob": { - "version": "12.33.0", - "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.33.0.tgz", - "integrity": "sha512-2SX8oP8PyblUcAFZSg39c8Ls+tFjavM6sBeV+qpw33mRzRhI/5hrFJmJ/x0H9xx5l6ECPvgSP8uPxqTeVbHNIA==", + "node_modules/@jest/test-result": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", + "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", "dev": true, "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.9.0", - "@azure/core-client": "^1.9.3", - "@azure/core-http-compat": "^2.2.0", - "@azure/core-lro": "^2.2.0", - "@azure/core-paging": "^1.6.2", - "@azure/core-rest-pipeline": "^1.19.1", - "@azure/core-tracing": "^1.2.0", - "@azure/core-util": "^1.11.0", - "@azure/core-xml": "^1.4.5", - "@azure/logger": "^1.1.4", - "@azure/storage-common": "^12.4.1", - "events": "^3.0.0", - "tslib": "^2.8.1" + "@jest/console": "^28.1.3", + "@jest/types": "^28.1.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" }, "engines": { - "node": ">=22.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/storage-blob/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "node_modules/@jest/test-sequencer": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", + "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@jest/test-result": "^28.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "slash": "^3.0.0" }, "engines": { - "node": ">=18.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/storage-blob/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/storage-common": { - "version": "12.4.1", - "resolved": "https://registry.npmjs.org/@azure/storage-common/-/storage-common-12.4.1.tgz", - "integrity": "sha512-t14unw/WofGDUi7TKJrsyXyPsN+NLgRm7hMaq0llxNmTIzt7f257+6LE6FKIJPh88zLj6M7LPvzve0fEYg/L3A==", + "node_modules/@jest/transform": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", + "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", "dev": true, "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.9.0", - "@azure/core-http-compat": "^2.2.0", - "@azure/core-rest-pipeline": "^1.24.0", - "@azure/core-tracing": "^1.2.0", - "@azure/core-util": "^1.11.0", - "@azure/logger": "^1.1.4", - "events": "^3.3.0", - "tslib": "^2.8.1" + "@babel/core": "^7.11.6", + "@jest/types": "^28.1.3", + "@jridgewell/trace-mapping": "^0.3.13", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^28.1.3", + "jest-regex-util": "^28.0.2", + "jest-util": "^28.1.3", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.1" }, "engines": { - "node": ">=20.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/storage-common/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "node_modules/@jest/types": { + "version": "28.1.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", + "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@jest/schemas": "^28.1.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.0.0" + "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/@azure/storage-common/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@azure/storage-queue": { - "version": "12.31.0", - "resolved": "https://registry.npmjs.org/@azure/storage-queue/-/storage-queue-12.31.0.tgz", - "integrity": "sha512-OzhvKO7G8+EXkUQPbKFkO7JNIAMkQZ9gMyZ85dsTCOk8jRRCbGbn3wsk3qrCAf6XWm+E2/fUsdLbX4YCOAWEKw==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@azure/abort-controller": "^2.1.2", - "@azure/core-auth": "^1.9.0", - "@azure/core-client": "^1.9.3", - "@azure/core-http-compat": "^2.0.0", - "@azure/core-paging": "^1.6.2", - "@azure/core-rest-pipeline": "^1.19.1", - "@azure/core-tracing": "^1.2.0", - "@azure/core-util": "^1.11.0", - "@azure/core-xml": "^1.4.3", - "@azure/logger": "^1.1.4", - "@azure/storage-common": "^12.4.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=22.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@azure/storage-queue/node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@azure/storage-queue/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/core/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "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.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", - "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "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-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.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "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/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@commander-js/extra-typings": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-10.0.3.tgz", - "integrity": "sha512-OIw28QV/GlP8k0B5CJTRsl8IyNvd0R8C8rfo54Yz9P388vCNDgdNrFlKxZTGqps+5j6lSw3Ss9JTQwcur1w1oA==", - "license": "MIT", - "peerDependencies": { - "commander": "10.0.x" - } - }, - "node_modules/@datadog/datadog-api-client": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/@datadog/datadog-api-client/-/datadog-api-client-1.60.0.tgz", - "integrity": "sha512-XmqT/TmkULWkeP2UJJVDWdXS8bdEOBYVlPI6MsSpRKViBwK3t20oPO5kTiTcO6bvipCahh/Yvqjjxeu9fR49HQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/buffer-from": "^1.1.0", - "@types/node": "*", - "@types/pako": "^1.0.3", - "buffer-from": "^1.1.2", - "cross-fetch": "^3.1.5", - "form-data": "^4.0.4", - "loglevel": "^1.8.1", - "pako": "^2.0.4" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", - "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/config-array/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", - "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", - "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@faker-js/faker": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.4.0.tgz", - "integrity": "sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0", - "npm": ">=10" - } - }, - "node_modules/@grpc/grpc-js": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", - "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.8.0", - "@js-sdsl/ordered-map": "^4.4.2" - }, - "engines": { - "node": ">=12.10.0" - } - }, - "node_modules/@grpc/proto-loader": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", - "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.5.5", - "yargs": "^17.7.2" - }, - "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", - "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@inquirer/ansi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", - "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/checkbox": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", - "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/confirm": { - "version": "5.1.21", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", - "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "10.3.2", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", - "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "cli-width": "^4.1.0", - "mute-stream": "^2.0.0", - "signal-exit": "^4.1.0", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@inquirer/editor": { - "version": "4.2.23", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", - "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/external-editor": "^1.0.3", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/expand": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", - "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", - "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^2.1.1", - "iconv-lite": "^0.7.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", - "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", - "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", - "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/number": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", - "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/password": { - "version": "4.0.23", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", - "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/prompts": { - "version": "7.10.1", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", - "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^4.3.2", - "@inquirer/confirm": "^5.1.21", - "@inquirer/editor": "^4.2.23", - "@inquirer/expand": "^4.0.23", - "@inquirer/input": "^4.3.1", - "@inquirer/number": "^3.0.23", - "@inquirer/password": "^4.0.23", - "@inquirer/rawlist": "^4.1.11", - "@inquirer/search": "^3.2.2", - "@inquirer/select": "^4.4.2" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/rawlist": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", - "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/search": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", - "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/select": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", - "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^1.0.2", - "@inquirer/core": "^10.3.2", - "@inquirer/figures": "^1.0.15", - "@inquirer/type": "^3.0.10", - "yoctocolors-cjs": "^2.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/type": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", - "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@isaacs/cliui": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", - "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "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": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.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": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@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": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.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/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "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/@jest/reporters/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^28.1.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": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "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/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "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==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/@jsep-plugin/assignment": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", - "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - }, - "peerDependencies": { - "jsep": "^0.4.0||^1.0.0" - } - }, - "node_modules/@jsep-plugin/regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", - "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - }, - "peerDependencies": { - "jsep": "^0.4.0||^1.0.0" - } - }, - "node_modules/@keyv/serialize": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", - "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@kwsites/file-exists": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", - "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1" - } - }, - "node_modules/@kwsites/file-exists/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@kwsites/promise-deferred": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", - "license": "MIT" - }, - "node_modules/@nodable/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-9uGyhaQavEUMC8AIddIjau4NsnsXhou+j5sBAGojCM1oxmQpVKTWR/9JxABD6UAv12vpIms55fPZKFQEhG6uBg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, - "node_modules/@oclif/core": { - "version": "4.11.14", - "resolved": "https://registry.npmjs.org/@oclif/core/-/core-4.11.14.tgz", - "integrity": "sha512-cZ5Ktd+rT0PO+o7KBH4vRFTgg+xMLf8F41WK39p8MkXEViZA/Qqe+4lzZT6102zgUxMORET1HtF9t5w8CB3tnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.2", - "ansis": "^3.17.0", - "clean-stack": "^3.0.1", - "cli-spinners": "^2.9.2", - "debug": "^4.4.3", - "ejs": "^3.1.10", - "get-package-type": "^0.1.0", - "indent-string": "^4.0.0", - "is-wsl": "^2.2.0", - "lilconfig": "^3.1.3", - "minimatch": "^10.2.5", - "semver": "^7.8.1", - "string-width": "^4.2.3", - "supports-color": "^8", - "tinyglobby": "^0.2.17", - "widest-line": "^3.1.0", - "wordwrap": "^1.0.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@oclif/core/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@oclif/core/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/@oclif/plugin-help": { - "version": "6.2.53", - "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-6.2.53.tgz", - "integrity": "sha512-njx2nTH87EQEEuz4ShNtL0gzzN981MRkDPqScbu+Tkd7NpIv30OHdTjQK1GzGtkf+V2RvSYIrX+LrLsUVh9DJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oclif/core": "^4" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@oclif/plugin-not-found": { - "version": "3.2.88", - "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-3.2.88.tgz", - "integrity": "sha512-5YTKSpuV77910/iGnGsj0fr9kOazCy/h1brki1CocbfawdvaVPedcwCH25wMcdRfo8mFD656bG9/kdki/+Wb9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/prompts": "^7.10.1", - "@oclif/core": "^4.11.7", - "ansis": "^3.17.0", - "fast-levenshtein": "^3.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@oclif/plugin-not-found/node_modules/fast-levenshtein": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", - "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fastest-levenshtein": "^1.0.7" - } - }, - "node_modules/@opensearch-project/opensearch": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.13.0.tgz", - "integrity": "sha512-Bu3jJ7pKzumbMMeefu7/npAWAvFu5W9SlbBow1ulhluqUpqc7QoXe0KidDrMy7Dy3BQrkI6llR3cWL4lQTZOFw==", - "license": "Apache-2.0", - "dependencies": { - "aws4": "^1.11.0", - "debug": "^4.3.1", - "hpagent": "^1.2.0", - "json11": "^2.0.0", - "ms": "^2.1.3", - "secure-json-parse": "^2.4.0" - }, - "engines": { - "node": ">=10", - "yarn": "^1.22.10" - } - }, - "node_modules/@opensearch-project/opensearch/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", - "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.218.0.tgz", - "integrity": "sha512-fmEWp5kXlGEc3i/lR698Hz41DfGyN4Tbe4g7L1AxSc7fF8Xeh/FQ9Quqpa9dVA413Q1Ad43QOLzU4JoXgbFPWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.9.0.tgz", - "integrity": "sha512-OQ0vzvbZBiUhjqLnUaoNfYmP8553Crr3aggB4y0ZUi815mZ7idpdJXQmoKdeBKJelYttoBlLSSHubmyw3wvX4w==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", - "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-grpc/-/exporter-metrics-otlp-grpc-0.218.0.tgz", - "integrity": "sha512-YapQ9vNMX0NSZF6LK5pWAFfjpJleV2O9uYWfYGeb/5F1Kb9rPGK8tZDMJFa/sOksgdFuflDvYuA0B4qjDB4fjQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/exporter-metrics-otlp-http": "0.218.0", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-metrics": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-grpc/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", - "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-http/-/exporter-metrics-otlp-http-0.218.0.tgz", - "integrity": "sha512-bV7d2OuMpZu2+gAaxUAhzfZ0h3WVZk8ETQUEE3DNSntbTaMpuITjtm8I0rNyHFdm7Ax57K6ty7SgFXlBmOLIvQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-metrics": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-http/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", - "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-metrics-otlp-proto/-/exporter-metrics-otlp-proto-0.218.0.tgz", - "integrity": "sha512-ubLddKjWULhla9YZRCj/rTBeppjJYE4e9w0icx5mTu3eFhWjQzbV75NYjXuIlEG+NJsBl6d+sTFw5Qu+oej4oQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/exporter-metrics-otlp-http": "0.218.0", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-metrics": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-metrics-otlp-proto/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", - "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-grpc/-/exporter-trace-otlp-grpc-0.218.0.tgz", - "integrity": "sha512-3fXxVQEj9TNAFaCi79JeFKfeLd0sDtInaR3gaZDVlzNSPHtz8PZuCV34JKWjD4XXzT20IdMe8IpX6mRVNDA4Tw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-grpc-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-grpc/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", - "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-http/-/exporter-trace-otlp-http-0.218.0.tgz", - "integrity": "sha512-8dqezsmPhtKitIK/eTipZhYl9EX2/gNQ5zUMhaz3uxEURwfkNf8IPvo6yNfrzbxdtpAOybS/+h7wmIWYqFSpiw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-http/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", - "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.218.0.tgz", - "integrity": "sha512-r1Msf8SNLRmwh9J6XQ5uh82D7CdDWMNHnPB7LAVHjzut0TkSeKc5KcIvr4SvHvfk/xwN5gxC+VLKQ1k0o8PSPw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", - "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-zipkin": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-zipkin/-/exporter-zipkin-2.9.0.tgz", - "integrity": "sha512-RwINoce2BH8T4obT5pMcAla2sWma1YZvYuaktWmTluQ0PkQdvv5D060rWI1+kawX+J2qBRcMbwrZJJNcMJUauQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.9.0", - "@opentelemetry/resources": "2.9.0", - "@opentelemetry/sdk-trace": "2.9.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.0.0" - } - }, - "node_modules/@opentelemetry/exporter-zipkin/node_modules/@opentelemetry/core": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", - "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.218.0.tgz", - "integrity": "sha512-ZwqpkNL5W7RyGJPDZ9g06DvKp8KFTWPJPN12anpMQYSKpTSU0z3EIZuPq9vPGpS8siFyOqDYDAuCwlNO9FqgbA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-transformer": "0.218.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-grpc-exporter-base": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-grpc-exporter-base/-/otlp-grpc-exporter-base-0.218.0.tgz", - "integrity": "sha512-H/lCGJ536N98VpYJOaWTQOkv4Dx6TnmStK6Rqfu1W7KkFbPAx04hjdYEMZF/YbnHzPUSIK4kM6OE2GKGBTpV9A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "^1.14.3", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/otlp-exporter-base": "0.218.0", - "@opentelemetry/otlp-transformer": "0.218.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.218.0.tgz", - "integrity": "sha512-CFaKH87WAzjuJ4awowTTLzUvMfaRfiOFG5+qm5S5ncyalRtN4ecQ+YmuANJSCrVPuvZFEkUgKhBPBndxi3rHsQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.218.0", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/sdk-logs": "0.218.0", - "@opentelemetry/sdk-metrics": "2.7.1", - "@opentelemetry/sdk-trace-base": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-metrics": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.7.1.tgz", - "integrity": "sha512-MpDJdkiFDs3Pm1RHO3KByuZbuBdJEXEAkiC0+yJdsZGVCdf1RpHR6n+LHDcS7ffmfrt5kVCzJSCfm4z2C7v0uQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz", - "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.9.0.tgz", - "integrity": "sha512-jyA5MBLQ+Dkl3+JsZkUoUvL7yHvU64kLsvpXKarWm6347Sl1t1bXFTFykUePNpT5WH5pm9a2Qtt03iIYQhZ1Fg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.9.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", - "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs": { - "version": "0.218.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.218.0.tgz", - "integrity": "sha512-QvnNdugatFTVCJXH0Mcu7GOOJSylA9j127kIezOE4YwTI4YbowRons2K4WZTv5FMS8T4q9P0NdaRHdkSmeAIag==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.218.0", - "@opentelemetry/core": "2.7.1", - "@opentelemetry/resources": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", - "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.7.1", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.9.0.tgz", - "integrity": "sha512-Xx8RGS4H5XEBl01WuCreMIpiah9cCXMbSkeuIePPdD2cUpq/vUzYmj8E/MK1OsbOc93FuAD4jfn2WOacKwLn7Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.9.0", - "@opentelemetry/resources": "2.9.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.9.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/core": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", - "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace/-/sdk-trace-2.9.0.tgz", - "integrity": "sha512-sGA19HvtrrSKYsseHphluH6j3p6Xa3fqc7c7y8f/7mYWejc1lyDFcpSdD1kYa50HCLUeEo4zA5bW0pniaPszuw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.9.0", - "@opentelemetry/resources": "2.9.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.9.0.tgz", - "integrity": "sha512-cp9zmTl62R8PJrpvFcmc8N2JQU/xfa0S+61q511Nji+QxCfZ8Ifvg7H27G8cANe4crg4RTrWsVvanHiXjSp6ag==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "2.9.0", - "@opentelemetry/resources": "2.9.0", - "@opentelemetry/sdk-trace": "2.9.0", - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", - "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace/node_modules/@opentelemetry/core": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.9.0.tgz", - "integrity": "sha512-m2nckMT80NnmjTYSPjJQObBJ+8dgkoajEOUbznL8AHZ3T3yHRk2P7gI1PhEBc1+lOnrYE9UWrWHqJDsmqjmNbw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "^1.29.0" - }, - "engines": { - "node": "^18.19.0 || >=20.6.0" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.42.0.tgz", - "integrity": "sha512-icc5xCzndZfhuJMy5oqk5AvloWquR7jtae74qzpkKkhGp8BivK+oCcEXgGnjCdTfp8hA44l+w8gE8yYJbocJJw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@playwright/browser-chromium": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/@playwright/browser-chromium/-/browser-chromium-1.61.0.tgz", - "integrity": "sha512-bBGzN+jow5uQtYRDCnFv5cpyMjUYTF6WnsfRRrDXIwbaGDtdh0JdOPmlKdJH4OK7TbkrZ35cV7jhQ1zELoX5ew==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.61.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@playwright/test": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.0.tgz", - "integrity": "sha512-cKA5B6lpFEMyMGjxF54QihfYpB4FkEGH+qZhtArDEG+wezQAJY8Pq6C7T1SjWz+FFzt3TbyoXBQYk/0292TdJA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.61.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", - "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", - "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", - "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1" - } - }, - "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==", - "dev": true, - "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==", - "dev": true, - "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==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", - "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true, - "license": "Apache-2.0" - }, - "node_modules/@sec-ant/readable-stream": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@simple-git/args-pathspec": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", - "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", - "license": "MIT" - }, - "node_modules/@simple-git/argv-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", - "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", - "license": "MIT", - "dependencies": { - "@simple-git/args-pathspec": "^1.0.3" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.24.51", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sindresorhus/is": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", - "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", - "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@smithy/core": { - "version": "3.29.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.2.tgz", - "integrity": "sha512-DXUk6yU0C1Q1tYvJh1VCtl8QOBcSoZpKwjTPkxT6A4MUQYHvgeKGByL8mrEdxnvhdf9nq5GyzmRb5n/vPgu3Lw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/core/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.4.7", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.7.tgz", - "integrity": "sha512-UEMLOoA0Fl4uYBxh6l0uN0H6EJe/A89OGeDNTteQeXpJ20BcpfIr4wlCY9pel1jEAUHAxaYwuqrYlrKdXE1GKQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.6.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.4.tgz", - "integrity": "sha512-psnst7NZWdAEvJvyW8YZEE7xNVMyLrQFfHtyrVFrxNyy+dKWkQ+rqC6oI5ZhxThpUy9RSfEshgm34zqbOxzsRw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/middleware-compression": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-compression/-/middleware-compression-4.5.7.tgz", - "integrity": "sha512-+6KtzRfzBNLQHQXmTBnUJyUAaq8cYp5GPGJSab3ahsVEgdnC/p/TO3BAyvXw9I5F+hDnwjUAV1EGQCD4NcQOiw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "fflate": "0.8.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/middleware-compression/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/node-http-handler": { - "version": "4.9.4", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.4.tgz", - "integrity": "sha512-BNTop/fSOptmoVk8g+efwHCofFh37g70OWGAFES1TeAAJja1K5aAI8rTE26ETSc5k8IQuWY2kAIoPla01NgYrA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/node-http-handler/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/signature-v4": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.3.tgz", - "integrity": "sha512-8qVKKzqh7naF27ePmx0SkUfnGP/wBI9dyaeAmhHvopnbIlItUAmB/e6PkPCU3rRb2v9BY8D4EZXSoydSibatvw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.29.2", - "@smithy/types": "^4.16.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/signature-v4/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@smithy/types": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.0.tgz", - "integrity": "sha512-aVUabzlBBmY0PfvVgLKQSOGFIL5/7R54JE3uD9a5Ay/jSED61SkuAcCYENNXJzYUvJ1NPrWO0P+rAXHCkbBUKw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@smithy/types/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "dev": true, - "license": "MIT" - }, - "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.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/buffer-from": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@types/buffer-from/-/buffer-from-1.1.3.tgz", - "integrity": "sha512-2lq4YC9uLUMGHkl2IDtX4tCXSo2+hwMpOJcY1qiIk1kybc31rIlPyM1HCVJhkPFIo75a/pOVxqyvwuf5TpCG/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "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-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", - "dev": true, - "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/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", - "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/pako": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/pako/-/pako-1.0.7.tgz", - "integrity": "sha512-YBtzT2ztNF6R/9+UXj2wTGFnC9NklAnASt3sC0h2m1bbH7G6FyBIkt4AN8ThZpNfxUo1b2iMVO0UawiJymEt8A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true, - "license": "MIT" - }, - "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/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "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/@typespec/ts-http-runtime": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.6.tgz", - "integrity": "sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og==", - "dev": true, - "license": "MIT", - "dependencies": { - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@typespec/ts-http-runtime/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@typespec/ts-http-runtime/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@typespec/ts-http-runtime/node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, - "node_modules/@upstash/redis": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.38.0.tgz", - "integrity": "sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" - } - }, - "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/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/adm-zip": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", - "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", - "license": "MIT", - "engines": { - "node": ">=12.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-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==", - "dev": true, - "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==", + "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", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ansis": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", - "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - } - }, - "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": ">=6.0.0" } }, - "node_modules/anynum": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", - "integrity": "sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], "license": "MIT" }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { - "sprintf-js": "~1.0.2" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/array-flatten": { + "node_modules/@kwsites/file-exists": { "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/arrivals": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/arrivals/-/arrivals-2.1.2.tgz", - "integrity": "sha512-g3+rxhxUen2H4+PPBOz6U6pkQ4esBuQPna1rPskgK1jamBdDZeoppyB2vPUM/l0ccunwRrq4r2rKgCvc2FnrFA==", - "dev": true, - "license": "ISC", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "license": "MIT", "dependencies": { - "debug": "^4.0.1", - "nanotimer": "0.3.14" + "debug": "^4.1.1" } }, - "node_modules/arrivals/node_modules/debug": { + "node_modules/@kwsites/file-exists/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4994,95 +1242,34 @@ } } }, - "node_modules/artillery": { - "version": "2.0.33", - "resolved": "https://registry.npmjs.org/artillery/-/artillery-2.0.33.tgz", - "integrity": "sha512-7CiejAL4/stcm9o4GJ8yq1xAKNR39pgVmy2hvb+s9IBCMRzmSYSaRk1Lv/3nWxRtzan0C1LK62lM09MZDKJsfA==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "@artilleryio/int-commons": "2.24.0", - "@artilleryio/int-core": "2.28.0", - "@aws-sdk/client-cloudwatch": "^3.1034.0", - "@aws-sdk/client-cloudwatch-logs": "^3.1034.0", - "@aws-sdk/client-ec2": "^3.1034.0", - "@aws-sdk/client-ecs": "^3.1034.0", - "@aws-sdk/client-iam": "^3.1034.0", - "@aws-sdk/client-lambda": "^3.1034.0", - "@aws-sdk/client-s3": "^3.1034.0", - "@aws-sdk/client-sqs": "^3.1034.0", - "@aws-sdk/client-ssm": "^3.1034.0", - "@aws-sdk/client-sts": "^3.1034.0", - "@aws-sdk/credential-providers": "^3.1034.0", - "@azure/arm-containerinstance": "^9.1.0", - "@azure/identity": "^4.13.0", - "@azure/storage-blob": "^12.30.0", - "@azure/storage-queue": "^12.29.0", - "@oclif/core": "^4.8.0", - "@oclif/plugin-help": "^6.2.36", - "@oclif/plugin-not-found": "^3.2.73", - "@smithy/core": "^3.24.0", - "@upstash/redis": "^1.36.1", - "artillery-engine-playwright": "1.30.0", - "artillery-plugin-apdex": "1.24.0", - "artillery-plugin-ensure": "1.27.0", - "artillery-plugin-expect": "2.27.0", - "artillery-plugin-fake-data": "1.24.0", - "artillery-plugin-metrics-by-endpoint": "1.27.0", - "artillery-plugin-publish-metrics": "2.38.0", - "artillery-plugin-slack": "1.22.0", - "async": "^2.6.4", - "chalk": "^2.4.2", - "chokidar": "^3.6.0", - "ci-info": "^4.3.1", - "cli-table3": "^0.6.5", - "cross-spawn": "^7.0.6", - "csv-parse": "^4.16.3", - "debug": "^4.4.3", - "dotenv": "^16.6.1", - "driftless": "^2.0.3", - "esbuild-wasm": "^0.19.12", - "eventemitter3": "^5.0.4", - "fs-extra": "^11.3.3", - "got": "^14.6.6", - "joi": "^17.13.3", - "js-yaml": "^3.14.1", - "jsonwebtoken": "^9.0.3", - "lodash": "^4.18.0", - "moment": "^2.30.1", - "nanoid": "^3.3.4", - "ora": "^4.0.4", - "rc": "^1.2.8", - "sqs-consumer": "6.0.2", - "tempy": "3.1.0", - "walk-sync": "^0.2.3", - "yaml-js": "^0.3.1" - }, - "bin": { - "artillery": "bin/run" - }, - "engines": { - "node": ">= 22.13.0" - } + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "license": "MIT" }, - "node_modules/artillery-engine-playwright": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/artillery-engine-playwright/-/artillery-engine-playwright-1.30.0.tgz", - "integrity": "sha512-Anc9SJUL/969XTUjEGzYiTZMCg7C39VNpVNLCDgkvZ8DmGdDqcqgXl6yTymwzyJpJE7leo3x5t/r5ADUYLPdDw==", - "dev": true, - "license": "MPL-2.0", + "node_modules/@opensearch-project/opensearch": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.13.0.tgz", + "integrity": "sha512-Bu3jJ7pKzumbMMeefu7/npAWAvFu5W9SlbBow1ulhluqUpqc7QoXe0KidDrMy7Dy3BQrkI6llR3cWL4lQTZOFw==", + "license": "Apache-2.0", "dependencies": { - "@playwright/browser-chromium": "1.61.0", - "@playwright/test": "1.61.0", - "debug": "^4.4.3", - "playwright": "1.61.0" + "aws4": "^1.11.0", + "debug": "^4.3.1", + "hpagent": "^1.2.0", + "json11": "^2.0.0", + "ms": "^2.1.3", + "secure-json-parse": "^2.4.0" + }, + "engines": { + "node": ">=10", + "yarn": "^1.22.10" } }, - "node_modules/artillery-engine-playwright/node_modules/debug": { + "node_modules/@opensearch-project/opensearch/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5096,413 +1283,333 @@ } } }, - "node_modules/artillery-plugin-apdex": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/artillery-plugin-apdex/-/artillery-plugin-apdex-1.24.0.tgz", - "integrity": "sha512-66sHtp8mtvSBkWfDaehMkERAEMc1NaOI6/i5hJikFP5iSZk3UC3PWBEeeyl1NGd39J1YpuFEA1aEn8ua048BYw==", + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@simple-git/args-pathspec": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", + "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "license": "MIT" + }, + "node_modules/@simple-git/argv-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", + "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "license": "MIT", + "dependencies": { + "@simple-git/args-pathspec": "^1.0.3" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.24.51", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", "dev": true, - "license": "MPL-2.0" + "license": "MIT" }, - "node_modules/artillery-plugin-ensure": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/artillery-plugin-ensure/-/artillery-plugin-ensure-1.27.0.tgz", - "integrity": "sha512-2ePl9SY/O8rrViqY+KLwMjCn5zVmgmAUo+30ATuisBsJcxZ+Z2g+NaMVZ0AVB8CZNGrqaIxTShbH42i/EPgVKA==", + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, - "license": "MPL-2.0", + "license": "BSD-3-Clause", "dependencies": { - "chalk": "^2.4.2", - "debug": "^4.4.3", - "filtrex": "^2.2.3" + "type-detect": "4.0.8" } }, - "node_modules/artillery-plugin-ensure/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" + "@sinonjs/commons": "^1.7.0" } }, - "node_modules/artillery-plugin-ensure/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "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": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/artillery-plugin-ensure/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "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": { - "color-name": "1.1.3" + "@babel/types": "^7.0.0" } }, - "node_modules/artillery-plugin-ensure/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/artillery-plugin-ensure/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/artillery-plugin-ensure/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.8.0" + "dependencies": { + "@babel/types": "^7.28.2" } }, - "node_modules/artillery-plugin-ensure/node_modules/filtrex": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/filtrex/-/filtrex-2.2.3.tgz", - "integrity": "sha512-TL12R6SckvJdZLibXqyp4D//wXZNyCalVYGqaWwQk9zucq9dRxmrJV4oyuRq4PHFHCeV5ZdzncIc/Ybqv1Lr6Q==", + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", "dev": true, "license": "MIT" }, - "node_modules/artillery-plugin-ensure/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==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/artillery-plugin-ensure/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==", + "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": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "@types/node": "*" } }, - "node_modules/artillery-plugin-expect": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/artillery-plugin-expect/-/artillery-plugin-expect-2.27.0.tgz", - "integrity": "sha512-ZdXoNRb5xvZCdZFQMzJsf8VY508WMUQ0ou3dkIqmQumAUD68lLNZMQM9j9yjaniG1ivSy4OCxWle52J5Et3WSA==", + "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": "SEE LICENSE IN LICENSE.txt", - "dependencies": { - "chalk": "^4.1.2", - "debug": "^4.4.3", - "jmespath": "^0.16.0", - "lodash": "^4.18.0" - } + "license": "MIT" }, - "node_modules/artillery-plugin-expect/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "@types/istanbul-lib-coverage": "*" } }, - "node_modules/artillery-plugin-fake-data": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/artillery-plugin-fake-data/-/artillery-plugin-fake-data-1.24.0.tgz", - "integrity": "sha512-Ft7GWaFCxRKUzlR1wBpNo6mJgmTHTcN4F7HAye5vx+svJagsyD5UmlBHLYFatJ8DiFR75sJepQE/AMrhTpziHg==", + "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": "MPL-2.0", + "license": "MIT", "dependencies": { - "@faker-js/faker": "10.4.0" + "@types/istanbul-lib-report": "*" } }, - "node_modules/artillery-plugin-metrics-by-endpoint": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/artillery-plugin-metrics-by-endpoint/-/artillery-plugin-metrics-by-endpoint-1.27.0.tgz", - "integrity": "sha512-XQwWgDHyC59dxcONDTjVu9Q8s1HfVnaMGOQb19ll9Hovag3zH0k7M9LYxY9ViPkeJeckGbWoS1BsU3kHifRrzw==", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, - "license": "MPL-2.0", - "dependencies": { - "debug": "^4.4.3" - } + "license": "MIT" }, - "node_modules/artillery-plugin-metrics-by-endpoint/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "undici-types": "~7.18.0" } }, - "node_modules/artillery-plugin-publish-metrics": { - "version": "2.38.0", - "resolved": "https://registry.npmjs.org/artillery-plugin-publish-metrics/-/artillery-plugin-publish-metrics-2.38.0.tgz", - "integrity": "sha512-XatDrJU16dJpdOnCQQ/j446BRIVmepCh3bXvuiwWa7FsJ51eMippSFjBLDjZGPzTjVh/YKovdgd0hWWXVvVmnw==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "@aws-sdk/client-cloudwatch": "^3.1034.0", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/context-async-hooks": "^2.5.1", - "@opentelemetry/exporter-metrics-otlp-grpc": "^0.218.0", - "@opentelemetry/exporter-metrics-otlp-http": "^0.218.0", - "@opentelemetry/exporter-metrics-otlp-proto": "^0.218.0", - "@opentelemetry/exporter-trace-otlp-grpc": "^0.218.0", - "@opentelemetry/exporter-trace-otlp-http": "^0.218.0", - "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0", - "@opentelemetry/exporter-zipkin": "^2.5.1", - "@opentelemetry/resources": "^2.5.1", - "@opentelemetry/sdk-metrics": "^2.5.1", - "@opentelemetry/sdk-trace-base": "^2.5.1", - "@opentelemetry/semantic-conventions": "^1.39.0", - "async": "^2.6.4", - "datadog-metrics": "^0.12.1", - "debug": "^4.4.3", - "dogapi": "^2.8.4", - "got": "^14.6.6", - "hot-shots": "^10.2.1", - "mixpanel": "^0.18.0", - "opentracing": "^0.14.7", - "prom-client": "^14.2.0", - "semver": "^7.7.3", - "uuid": "^11.1.0" - } + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true, + "license": "MIT" }, - "node_modules/artillery-plugin-publish-metrics/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } + "license": "MIT" }, - "node_modules/artillery-plugin-slack": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/artillery-plugin-slack/-/artillery-plugin-slack-1.22.0.tgz", - "integrity": "sha512-Z7quNzgDeLEmOViDdM6tYmlMbXXlrDznG2hoJyXI6sO+/TFF8Ojsld/u0jEWNPmg01EIsqFncuT+ApfcCXG/eA==", + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, - "license": "MPL-2.0", + "license": "MIT", "dependencies": { - "debug": "^4.4.3", - "got": "^14.6.6" + "@types/yargs-parser": "*" } }, - "node_modules/artillery-plugin-slack/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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/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": { - "ms": "^2.1.3" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/artillery/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=4" + "node": ">=0.4.0" } }, - "node_modules/artillery/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/artillery/node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12.0" } }, - "node_modules/artillery/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/artillery/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/artillery/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "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": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" + "type-fest": "^0.21.3" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/artillery/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", - "dev": true, - "license": "BSD-2-Clause", "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://dotenvx.com" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/artillery/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=8" } }, - "node_modules/artillery/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==", + "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==", "dev": true, "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/artillery/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==", + "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": "MIT", + "license": "ISC", "dependencies": { - "has-flag": "^3.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=4" + "node": ">= 8" } }, - "node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "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": { - "lodash": "^4.17.14" + "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/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -5678,16 +1785,6 @@ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5701,28 +1798,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bintrees": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", - "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", - "dev": true, - "license": "MIT" - }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -5778,30 +1857,16 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true, - "license": "ISC" - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "dev": true, - "license": "MIT" - }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/braces": { @@ -5861,13 +1926,6 @@ "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==", - "dev": true, - "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", @@ -5875,35 +1933,6 @@ "dev": true, "license": "MIT" }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/byte-counter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/byte-counter/-/byte-counter-0.1.0.tgz", - "integrity": "sha512-jheRLVMeUKrDBjVw2O5+k4EvR4t9wtxHL+bo/LxfkxsVeuGMy3a5SEGgXdAFA4FSzTrU8rQXQIrsZ3oBq5a0pQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -5913,88 +1942,6 @@ "node": ">= 0.8" } }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "13.0.19", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-13.0.19.tgz", - "integrity": "sha512-SVXGH037+Mo1aIMO5B2UcleR43FGjFdN+M8JObSyEoQ2Mn4CODRWx28gN5jiTF0n5ItsgtIZfyargMNs8GX4kg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "^4.2.0", - "get-stream": "^9.0.1", - "http-cache-semantics": "^4.2.0", - "keyv": "^5.6.0", - "mimic-response": "^4.0.0", - "normalize-url": "^8.1.1", - "responselike": "^4.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sec-ant/readable-stream": "^0.4.1", - "is-stream": "^4.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/is-stream": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/keyv": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", - "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, - "node_modules/cacheable-request/node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "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", @@ -6092,57 +2039,6 @@ "node": ">=10" } }, - "node_modules/chardet": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", - "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", - "dev": true, - "license": "MIT" - }, - "node_modules/cheerio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", - "integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.1.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.19.0", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -6191,87 +2087,6 @@ "dev": true, "license": "MIT" }, - "node_modules/clean-stack": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-3.0.1.tgz", - "integrity": "sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clean-stack/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -6287,16 +2102,6 @@ "node": ">=12" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6405,27 +2210,6 @@ "node": ">= 0.6" } }, - "node_modules/cookie-parser": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", - "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cookie": "0.7.2", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-parser/node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cookie-signature": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", @@ -6442,103 +2226,27 @@ "vary": "^1" }, "engines": { - "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.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==", - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "node": ">= 0.10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "dev": true, - "license": "BSD-2-Clause", "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "node": ">= 8" } }, - "node_modules/csv-parse": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", - "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==", - "dev": true, - "license": "MIT" - }, "node_modules/cve-core": { "version": "2.2.0-rc4", "resolved": "git+ssh://git@github.com/CVEProject/cve-core.git#a1640b2979aebb681f151aa20248f87eb4fd8a81", @@ -6590,38 +2298,6 @@ "url": "https://dotenvx.com" } }, - "node_modules/datadog-metrics": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/datadog-metrics/-/datadog-metrics-0.12.1.tgz", - "integrity": "sha512-Gy+17ia7m9Uy+nKQHDd7fljdq0fqqfpgkpxlwW0x1oFKI7RcgDV32pMCfHtv4HKychP6fHtncj3Lf4VN/g4G6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@datadog/datadog-api-client": "^1.17.0", - "debug": "^4.1.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/datadog-metrics/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -6656,22 +2332,6 @@ "ms": "^2.1.1" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -6679,26 +2339,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-for-each": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/deep-for-each/-/deep-for-each-3.0.0.tgz", - "integrity": "sha512-pPN+0f8jlnNP+z90qqOdxGghJU5XM6oBDhvAR+qdQzjCg5pk/7VPPvKK1GqoXEFkHza6ZS+Otzzvmr0g3VUaKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.isplainobject": "^4.0.6" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -6716,62 +2356,6 @@ "node": ">=0.10.0" } }, - "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -6820,82 +2404,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/dogapi": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/dogapi/-/dogapi-2.8.4.tgz", - "integrity": "sha512-065fsvu5dB0o4+ENtLjZILvXMClDNH/yA9H6L8nsdcNiz9l0Hzpn7aQaCOPYXxqyzq4CRPOdwkFXUjDOXfRGbg==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.2", - "json-bigint": "^1.0.0", - "lodash": "^4.17.21", - "minimist": "^1.2.5", - "rc": "^1.2.8" - }, - "bin": { - "dogapi": "bin/dogapi" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/dotenv": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", @@ -6905,16 +2413,6 @@ "node": ">=6" } }, - "node_modules/driftless": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/driftless/-/driftless-2.0.3.tgz", - "integrity": "sha512-hSDKsQphnL4O0XLAiyWQ8EiM9suXH0Qd4gMtwF86b5wygGV8r95w0JcA38FOmx9N3LjFCIHLG2winLPNken4Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "present": "^0.0.3" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -6929,38 +2427,12 @@ "node": ">= 0.4" } }, - "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==", - "dev": true, - "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/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { "version": "1.5.381", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz", @@ -6981,131 +2453,20 @@ "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==", - "dev": true, - "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/encoding-sniffer": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/encoding-sniffer/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/engine.io-client": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", - "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.21.0", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-client/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io-client/node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/ensure-posix-path": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ensure-posix-path/-/ensure-posix-path-1.1.1.tgz", - "integrity": "sha512-VWU0/zXzVbeJNXvME/5EmLuEj2TauvoaTz6aFYK1Z92JCBlDlZ3Gu0tuGR42kpW1754ywTs+QB0g5TP0oj9Zaw==", - "dev": true, - "license": "ISC" - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "BSD-2-Clause", + "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.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">= 0.8" } }, "node_modules/error-ex": { @@ -7163,19 +2524,6 @@ "node": ">= 0.4" } }, - "node_modules/esbuild-wasm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.19.12.tgz", - "integrity": "sha512-Zmc4hk6FibJZBcTx5/8K/4jT3/oG1vkGTEeKJUQFCUQKimD6Q7+adp/bdVQyYJFolMKaXkQnVZdV4O5ZaTYmyQ==", - "dev": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -7486,23 +2834,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -7606,6 +2937,42 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "8.6.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.1.tgz", + "integrity": "sha512-0D493aP61w0TJ2A0wy27riRsO7FMQ7FK+KUHOKCSfPvYo0R55aiC6emCVgFUeShH0fq0ICPVzNcgoS+BsbXQCA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express-rate-limit/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/express-validator": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.3.1.tgz", @@ -7654,13 +3021,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "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", @@ -7682,57 +3042,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-xml-builder": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.1.tgz", - "integrity": "sha512-tPb5TTWfgfVx5BNSi2xV0eLr89POeXXn0dXIsCJ9m1narrWxeIyx6je9d7Rce/3NyXLbvuQmLkxq+RuxMWejvw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.9.3.tgz", - "integrity": "sha512-brCNCeScma/kqa54J4PIDriSSSLssRkuYaUCpvHJulGc3HGI/xxKUCTDcYkAdqJsyb//ydpbxecjC3hB9+tb/g==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.2.0", - "fast-xml-builder": "^1.2.0", - "is-unsafe": "^1.0.1", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.4.1", - "xml-naming": "^0.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -7743,37 +3052,12 @@ "bser": "2.1.1" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/fetch-retry": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz", "integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==", "license": "MIT" }, - "node_modules/fflate": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.1.tgz", - "integrity": "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -7787,37 +3071,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/filelist": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", - "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -7831,13 +3084,6 @@ "node": ">=8" } }, - "node_modules/filtrex": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/filtrex/-/filtrex-0.5.4.tgz", - "integrity": "sha512-2phGAjWOYRf96Al6s+w/hMjObP1cRyQ95hoZApjeFO75DXN4Flh9uuUAtL3LI4fkryLa2QWdA8MArvt0GMU0pA==", - "dev": true, - "license": "MIT" - }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -7958,16 +3204,6 @@ "node": ">= 6" } }, - "node_modules/form-data-encoder": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz", - "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 18" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7986,21 +3222,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-extra": { - "version": "11.3.6", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", - "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8174,85 +3395,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "14.6.6", - "resolved": "https://registry.npmjs.org/got/-/got-14.6.6.tgz", - "integrity": "sha512-QLV1qeYSo5l13mQzWgP/y0LbMr5Plr5fJilgAIwgnwseproEbtNym8xpLsDzeZ6MWXgNE6kdWGBjdh3zT/Qerg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^7.0.1", - "byte-counter": "^0.1.0", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^13.0.12", - "decompress-response": "^10.0.0", - "form-data-encoder": "^4.0.2", - "http2-wrapper": "^2.2.1", - "keyv": "^5.5.3", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^4.0.1", - "responselike": "^4.0.2", - "type-fest": "^4.26.1" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/decompress-response": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-10.0.0.tgz", - "integrity": "sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^4.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got/node_modules/keyv": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", - "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@keyv/serialize": "^1.1.1" - } - }, - "node_modules/got/node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -8309,19 +3451,6 @@ "node": ">= 0.4" } }, - "node_modules/hot-shots": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/hot-shots/-/hot-shots-10.2.1.tgz", - "integrity": "sha512-tmjcyZkG/qADhcdC7UjAp8D7v7W2DOYFgaZ48fYMuayMQmVVUg8fntKmrjes/b40ef6yZ+qt1lB8kuEDfLC4zw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "unix-dgram": "2.x" - } - }, "node_modules/hpagent": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-1.2.0.tgz", @@ -8338,46 +3467,6 @@ "dev": true, "license": "MIT" }, - "node_modules/htmlparser2": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", - "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "entities": "^7.0.1" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true, - "license": "BSD-2-Clause" - }, "node_modules/http-errors": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", @@ -8412,52 +3501,6 @@ "node": ">= 0.6" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/http-proxy-agent/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -8510,16 +3553,6 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -8579,16 +3612,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -8607,12 +3630,14 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" + "node_modules/ip-address": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.3.1.tgz", + "integrity": "sha512-1e9d3kb97NHJTIJDZW9rKqW2h6+dFa50Dy0fpPSMQp2ADje5gvKsXmdiK6dwY5t76TaTt5+P5N1Y/LoToIxP6g==", + "license": "MIT", + "engines": { + "node": ">= 12" + } }, "node_modules/ipaddr.js": { "version": "1.9.1", @@ -8659,22 +3684,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -8718,51 +3727,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container/node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -8798,32 +3762,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-unsafe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-unsafe/-/is-unsafe-1.0.1.tgz", - "integrity": "sha512-CLK2+VdgERgD96EYm5lUQssZYlRg2tkZnbsxZoacmSiRxiFJ4Nk4SzjCl+Ur+v3kXIY9dTIdb3IH22y1mZ56LA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -8944,31 +3882,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/jake": { - "version": "10.9.4", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", - "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.6", - "filelist": "^1.0.4", - "picocolors": "^1.1.1" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, "node_modules/jest": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", @@ -9622,30 +4535,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jmespath": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", - "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/joi": { - "version": "17.13.4", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", - "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9667,16 +4556,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsep": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", - "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.16.0" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -9690,16 +4569,6 @@ "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==", - "dev": true, - "license": "MIT", - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -9758,84 +4627,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", - "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonpath-plus": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", - "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsep-plugin/assignment": "^1.3.0", - "@jsep-plugin/regex": "^1.0.4", - "jsep": "^1.4.0" - }, - "bin": { - "jsonpath": "bin/jsonpath-cli.js", - "jsonpath-plus": "bin/jsonpath-cli.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", - "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", - "dev": true, - "license": "MIT", - "dependencies": { - "jws": "^4.0.1", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jwa": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", - "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", - "dev": true, - "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.1", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", - "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jwa": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9880,19 +4671,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9919,33 +4697,12 @@ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, - "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==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "license": "MIT" }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -9959,27 +4716,6 @@ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", "license": "MIT" }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -9988,160 +4724,28 @@ }, "node_modules/lodash.omit": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", - "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==", - "deprecated": "This package is deprecated. Use destructuring assignment syntax instead.", - "license": "MIT" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==", - "license": "MIT" - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "license": "MIT" - }, - "node_modules/lodash.unset": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/lodash.unset/-/lodash.unset-4.18.0.tgz", - "integrity": "sha512-7B95k5zjcJtuGuLqxOjT91g0Txwv1PWBlBDGzjW4kM8qgw2qfSgBo1QcIWGUpW9uN/1ZSKX9NHJzVJIgbeR5PQ==", - "license": "MIT" - }, - "node_modules/log-symbols": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^2.4.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-symbols/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/log-symbols/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/log-symbols/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" - } + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==", + "deprecated": "This package is deprecated. Use destructuring assignment syntax instead.", + "license": "MIT" }, - "node_modules/loglevel": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", - "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - }, - "funding": { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/loglevel" - } + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==", + "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==", - "dev": true, - "license": "Apache-2.0" + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "license": "MIT" }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/lodash.unset": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/lodash.unset/-/lodash.unset-4.18.0.tgz", + "integrity": "sha512-7B95k5zjcJtuGuLqxOjT91g0Txwv1PWBlBDGzjW4kM8qgw2qfSgBo1QcIWGUpW9uN/1ZSKX9NHJzVJIgbeR5PQ==", + "license": "MIT" }, "node_modules/lru-cache": { "version": "5.1.1", @@ -10179,29 +4783,6 @@ "tmpl": "1.0.5" } }, - "node_modules/matcher-collection": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/matcher-collection/-/matcher-collection-1.1.2.tgz", - "integrity": "sha512-YQ/teqaOIIfUHedRam08PB3NK7Mjct6BvzRnJmpGDm8uFXpNr1sbY4yuflI5JcEs6COpYA0FpRQhSDBf1tT95g==", - "dev": true, - "license": "ISC", - "dependencies": { - "minimatch": "^3.0.2" - } - }, - "node_modules/matcher-collection/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -10302,19 +4883,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -10330,16 +4898,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -10349,88 +4907,24 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mixpanel": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/mixpanel/-/mixpanel-0.18.1.tgz", - "integrity": "sha512-YD1xfn6WP6ZLQ6Pmgh0KgdXhueJEsrodThMTsHzHMH0VbWa9ck8s+ynDtM83OSgt+yQ61W/SQNrH8Y4wIwocGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "https-proxy-agent": "5.0.0" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/mixpanel/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/mixpanel/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mixpanel/node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/morgan": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", - "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.11.0.tgz", + "integrity": "sha512-zSkVu3t18r39pw4ixfBKvfZi3y2UOqr7d4WYwcj3m8nXpEQK4rPO6GLzs/CExoRgmX3y9EjmmcXqv6jq0SK46g==", "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", "depd": "~2.0.0", - "on-finished": "~2.3.0", + "on-finished": "~2.4.1", "on-headers": "~1.1.0" }, "engines": { "node": ">= 0.8.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/morgan/node_modules/debug": { @@ -10448,68 +4942,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "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/mute-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", - "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/nan": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", - "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/nanotimer": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/nanotimer/-/nanotimer-0.3.14.tgz", - "integrity": "sha512-NpKXdP6ZLwZcODvDeyfoDBVoncbrgvC12txO3F4l9BxMycQjZD29AnasGAy7uSi3dcsTGnGn6/zzvQRwbjS4uw==", - "dev": true, - "license": "ISC" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -10526,27 +4964,6 @@ "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==", - "dev": true, - "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", @@ -10644,19 +5061,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.1.tgz", - "integrity": "sha512-JYc0DPlpGWB40kH5g07gGTrYuMqV653k3uBKY6uITPWds3M0ov3GaWGp9lbE3Bzngx8+XkfzgvASb9vk9JDFXQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -10670,19 +5074,6 @@ "node": ">=8" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -10749,107 +5140,24 @@ }, "funding": { "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opentracing": { - "version": "0.14.7", - "resolved": "https://registry.npmjs.org/opentracing/-/opentracing-0.14.7.tgz", - "integrity": "sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.1.1.tgz", - "integrity": "sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^3.0.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.2.0", - "is-interactive": "^1.0.0", - "log-symbols": "^3.0.0", - "mute-stream": "0.0.8", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true, - "license": "ISC" + } }, - "node_modules/p-cancelable": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-4.0.1.tgz", - "integrity": "sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, "engines": { - "node": ">=14.16" + "node": ">= 0.8.0" } }, "node_modules/p-limit": { @@ -10913,23 +5221,6 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, - "node_modules/pako": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.2.0.tgz", - "integrity": "sha512-zJq6RP/5q+TO2OpFV3FHzlPnFjmkb7Nc99a5SNjJE+uu/PkpChs+NIZSSzbBoD+6kjiISXjfYdwj1ZRQ81dz/w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], - "license": "(MIT AND Zlib)" - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -10949,59 +5240,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -11021,22 +5259,6 @@ "node": ">=8" } }, - "node_modules/path-expression-matcher": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.6.2.tgz", - "integrity": "sha512-enSlaiat05iasnzmgNxRj8reFdj3puY2QpNgP1aPIaVfT6nn9ICuPoFlKHk8EN22HcwewshO+mN2DGbkCEOtqQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -11137,53 +5359,6 @@ "node": ">=8" } }, - "node_modules/playwright": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.0.tgz", - "integrity": "sha512-Z+7BeeqQPRRzklHsVFP4KTGIyMxKUmfeRA4WisM6G3/XW6nwGeX6fX9qYaDa+CiUqpOkb2f6X3nar05R3kSuJQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.61.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.61.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.0.tgz", - "integrity": "sha512-caX7TrY3Ml6egyDX0WUcTHDxodl/b51y5wJOdCEA36QviK/s2g081hvmGs8eaE3DWb6NYZQ6BjO/QkNRPenoPA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11194,13 +5369,6 @@ "node": ">= 0.8.0" } }, - "node_modules/present": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/present/-/present-0.0.3.tgz", - "integrity": "sha512-d0QMXYTKHuAO0n0IfI/x2lbNwybdNWjRQ08hQySzqMQ2M0gwh/IetTv2glkPJihFn+cMDYjK/BiVgcLcjsASgg==", - "dev": true, - "license": "MIT" - }, "node_modules/prettier": { "version": "3.9.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.3.tgz", @@ -11246,19 +5414,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/prom-client": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", - "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tdigest": "^0.1.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -11273,30 +5428,6 @@ "node": ">= 6" } }, - "node_modules/protobufjs": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", - "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", - "dev": true, - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.1", - "@protobufjs/fetch": "^1.1.1", - "@protobufjs/float": "^1.0.2", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.1", - "@types/node": ">=13.7.0", - "long": "^5.3.2" - }, - "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", @@ -11351,19 +5482,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -11408,32 +5526,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -11485,13 +5577,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true, - "license": "MIT" - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -11525,43 +5610,6 @@ "node": ">=10" } }, - "node_modules/responselike": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-4.0.2.tgz", - "integrity": "sha512-cGk8IbWEAnaCpdAt1BHzJ3Ahz5ewDJa0KseTsE3qIRMJ3C698W8psM7byCeWVpd/Ha7FUYzuRVzXoKoM6nRUbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/restore-cursor/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/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -11614,19 +5662,6 @@ "node": "*" } }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -11920,79 +5955,6 @@ "node": ">=8" } }, - "node_modules/socket.io-client": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", - "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-client/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socketio-wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/socketio-wildcard/-/socketio-wildcard-2.0.0.tgz", - "integrity": "sha512-Bf3ioZq15Z2yhFLDasRvbYitg82rwm+5AuER5kQvEQHhNFf4R4K5o/h57nEpN7A59T9FyRtTj34HZfMWAruw/A==", - "dev": true, - "license": "MIT" - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -12019,39 +5981,7 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/sqs-consumer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/sqs-consumer/-/sqs-consumer-6.0.2.tgz", - "integrity": "sha512-Y8ztFBc1VPj4q72j9Uji4XR6n2O88n9X83aOUaCvz9zZqjpGJaI4PBHBPnE8RDBgD0EQR8Dh+sQwH/+8mzr+Bg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sqs": "^3.226.0", - "debug": "^4.3.4" - }, - "peerDependencies": { - "@aws-sdk/client-sqs": "^3.226.0" - } - }, - "node_modules/sqs-consumer/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } + "license": "BSD-3-Clause" }, "node_modules/stack-utils": { "version": "2.0.6", @@ -12150,22 +6080,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.1.tgz", - "integrity": "sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "anynum": "^1.0.1" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12278,71 +6192,6 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, - "node_modules/tdigest": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", - "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bintrees": "1.0.2" - } - }, - "node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/tempy": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", - "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-stream": "^3.0.0", - "temp-dir": "^3.0.0", - "type-fest": "^2.12.2", - "unique-string": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tempy/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -12410,43 +6259,6 @@ "node": "*" } }, - "node_modules/tinyglobby": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, - "license": "MIT" - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -12486,26 +6298,6 @@ "nodetouch": "bin/nodetouch.js" } }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, "node_modules/ts-pattern": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-4.3.0.tgz", @@ -12567,13 +6359,6 @@ "node": ">= 0.6" } }, - "node_modules/uncrypto": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", - "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", - "dev": true, - "license": "MIT" - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -12581,16 +6366,6 @@ "dev": true, "license": "MIT" }, - "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, "node_modules/undici-types": { "version": "7.18.2", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", @@ -12598,48 +6373,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unique-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "crypto-random-string": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unix-dgram": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/unix-dgram/-/unix-dgram-2.0.7.tgz", - "integrity": "sha512-pWaQorcdxEUBFIKjCqqIlQaOoNVmchyoaNAJ/1LwyyfK2XSxcBhgJNiSE8ZRhR0xkNGyk4xInt1G03QPoKXY5A==", - "dev": true, - "hasInstallScript": true, - "license": "ISC", - "optional": true, - "dependencies": { - "bindings": "^1.5.0", - "nan": "^2.20.0" - }, - "engines": { - "node": ">=0.10.48" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -12699,20 +6432,6 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", - "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/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", @@ -12753,17 +6472,6 @@ "node": ">= 0.8" } }, - "node_modules/walk-sync": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/walk-sync/-/walk-sync-0.2.7.tgz", - "integrity": "sha512-OH8GdRMowEFr0XSHQeX5fGweO6zSVHo7bG/0yJQx6LAj9Oukz0C8heI3/FYectT66gY0IPGe89kOvU410/UNpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ensure-posix-path": "^1.0.0", - "matcher-collection": "^1.0.0" - } - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -12774,71 +6482,6 @@ "makeerror": "1.0.12" } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, - "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==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "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==", - "dev": true, - "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", @@ -12854,19 +6497,6 @@ "node": ">= 8" } }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -12877,13 +6507,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -12930,85 +6553,6 @@ "dev": true, "license": "ISC" }, - "node_modules/ws": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", - "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wsl-utils/node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -13026,16 +6570,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml-js": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/yaml-js/-/yaml-js-0.3.1.tgz", - "integrity": "sha512-LjoIFHCtSfkGzPsmYmfDsW+TShtQBcY7lwH1yLQ5brJHXRhjteUnVE2ThCbz1madq8JUZIAjFiavfnIFeTO8CQ==", - "dev": true, - "license": "WTFPL", - "engines": { - "node": ">=8" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -13077,19 +6611,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", - "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index 88265fc..2a59a9f 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,8 @@ { "name": "cve-search-api", - "version": "0.3.0-beta", + "version": "0.4.0-beta", "scripts": { "dev": "node routes/swagger.js && nodemon server.js", - "artillery:contract": "artillery run test/artillery/contract.yml", - "artillery:load": "artillery run test/artillery/load.yml", - "artillery:smoke": "artillery run test/artillery/smoke.yml", - "artillery:stress": "artillery run test/artillery/stress.yml", "coverage": "jest --coverage", "format": "prettier --write .", "format:check": "prettier --check .", @@ -23,14 +19,14 @@ "debug": "~3.2.7", "dotenv": "^6.1.0", "express": "^4.22.2", + "express-rate-limit": "^8.6.1", "express-validator": "^7.2.1", "http-errors": "~1.8.1", - "morgan": "~1.10.0", + "morgan": "~1.11.0", "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@eslint/js": "^10.0.1", - "artillery": "^2.0.33", "eslint": "^10.6.0", "globals": "^17.7.0", "jest": "^28.1.3", @@ -40,7 +36,7 @@ }, "overrides": { "axios": "1.18.0", - "brace-expansion": "5.0.6", + "brace-expansion": "5.0.8", "lodash": "4.18.1", "lodash.unset": "4.18.0", "path-to-regexp": "0.1.13", diff --git a/routes/index.js b/routes/index.js index bab6f82..adca945 100644 --- a/routes/index.js +++ b/routes/index.js @@ -4,8 +4,13 @@ const errors = require('../utils/errors'); const mw = require('../controllers/middleware'); const controller = require('../controllers/searchController'); const constants = require('../utils/constants'); +const featureFlags = require('../utils/featureFlags'); +const searchDeadline = require('../utils/searchDeadline'); +const searchProvider = require('../utils/searchProvider'); +const searchRateLimiter = require('../utils/searchRateLimiter'); const error = new errors.errors(); +const limitSearchRequests = searchRateLimiter.createSearchRateLimiter(); const SEARCH_BODY_FIELDS = new Set(constants.SEARCH_QUERY_PARAMS); const SEARCH_STRING_BODY_FIELDS = constants.SEARCH_QUERY_PARAMS.filter( (field) => @@ -18,6 +23,30 @@ function healthCheck(req, res) { res.status(200).send(); } +async function readinessCheck(req, res) { + const lifecycle = searchDeadline.createSearchRequestLifecycle( + req, + res, + searchProvider.getOpenSearchReadinessTimeoutMs() + ); + + try { + await searchProvider.checkOpenSearchReadiness({ + signal: lifecycle.signal + }); + lifecycle.throwIfAborted(); + + return res.status(200).send(); + } catch (err) { + if (searchDeadline.isSearchClientDisconnectedError(err)) return; + + if (typeof res.set === 'function') res.set('Retry-After', '5'); + return res.status(503).json(error.searchNotReadyError()); + } finally { + lifecycle.complete(); + } +} + function parseError(req, res, next) { const err = validationResult(req).formatWith(({ location, msg, param, value, nestedErrors }) => { return msg; @@ -92,6 +121,28 @@ function rejectSearchQueryParams(req, res, next) { next(); } +function hasRequestBody(req) { + const contentLength = Number(req.headers['content-length']); + + return req.headers['transfer-encoding'] !== undefined || (Number.isFinite(contentLength) && contentLength > 0); +} + +function requireSearchJsonContentType(req, res, next) { + if (hasRequestBody(req) && !req.is('application/json')) { + return res.status(415).json(error.searchContentTypeError()); + } + + next(); +} + +function requireWebSearchEnabled(req, res, next) { + if (!featureFlags.isWebSearchEnabled()) { + return res.status(410).json(error.webSearchDisabledError()); + } + + next(); +} + function normalizeSearchParams(req, res, next) { const searchParams = { ...(req.body ?? {}) }; @@ -139,7 +190,6 @@ router.post( cvssV3_0Metrics: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", cvssV3_1Metrics: "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", cvssV4Metrics: "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N", - sourceIdentifier: "security@example.org", cpeName: "cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*", isVulnerable: false, lastModStartDate: "2024-01-01T00:00:00.000Z", @@ -186,11 +236,64 @@ router.post( } #swagger.responses[200] = { description: 'A list of matching CVE records', +headers: { + "X-Request-ID": { + description: "Request correlation ID", + schema: { + type: "string" + } + } +} +} +#swagger.responses[400] = { +description: 'Malformed JSON, invalid request body, parameter combination, or version range', } #swagger.responses[404] = { description: 'Invalid cpeName value', } +#swagger.responses[415] = { +description: 'A non-empty request body did not use the application/json media type with UTF-8 encoding', +} +#swagger.responses[429] = { +description: 'Application rate limit exceeded, or the cold CPE search queue is full or exceeded its maximum wait', +headers: { + "Retry-After": { + description: "Seconds until a rate-limited request can be retried", + schema: { + type: "integer" + } + }, + "RateLimit": { + description: "Current application rate-limit state when application limiting is enabled", + schema: { + type: "string" + } + }, + "RateLimit-Policy": { + description: "Application rate-limit policy when application limiting is enabled", + schema: { + type: "string" + } + } +} +} +#swagger.responses[503] = { +description: 'OpenSearch is unavailable', +headers: { + "Retry-After": { + description: "Seconds before retrying an unavailable OpenSearch dependency", + schema: { + type: "integer" + } + } +} +} +#swagger.responses[504] = { +description: 'Search exceeded its OpenSearch or overall processing deadline', +} */ + requireSearchJsonContentType, + limitSearchRequests, rejectSearchQueryParams, body().custom(isSearchBodyObject).withMessage(error.searchBodyError()), body().custom(hasOnlySearchBodyFields), @@ -222,29 +325,77 @@ description: 'Invalid cpeName value', controller.SEARCH ); -// Endpoint used for AWS health checks +// Process liveness endpoint router.get( '/api/health-check', /* #swagger.operationId = 'healthCheck' -#swagger.summary = "Health check for AWS monitoring" -#swagger.description = "Health check for AWS monitoring" +#swagger.summary = "Process liveness check" +#swagger.description = "Returns 200 when the API process is running. This endpoint does not check OpenSearch." #swagger.responses[200] = { description: 'OK', +headers: { + "X-Request-ID": { + description: "Request correlation ID", + schema: { + type: "string" + } + } +} } */ healthCheck ); +// OpenSearch dependency readiness endpoint +router.get( + '/api/ready-check', + /* +#swagger.operationId = 'readinessCheck' +#swagger.summary = "OpenSearch readiness check" +#swagger.description = "Sends a minimal zero-result search to the configured CVE index. Returns 200 when the index is available with search permission, or a sanitized 503 response when configuration, connectivity, timeout, permission, or index availability checks fail." +#swagger.responses[200] = { +description: 'API is ready to serve search requests', +headers: { + "X-Request-ID": { + description: "Request correlation ID", + schema: { + type: "string" + } + } +} +} +#swagger.responses[503] = { +description: 'Search service is not ready', +headers: { + "Retry-After": { + description: "Seconds before retrying the readiness check", + schema: { + type: "integer" + } + }, + "X-Request-ID": { + description: "Request correlation ID", + schema: { + type: "string" + } + } +} +} +*/ + readinessCheck +); + router.post( '/webSearch', /* #swagger.tags = ['CVE Record'] #swagger.operationId = 'webSearch' -#swagger.summary = "Retrieves all matching CVE records after applying the JSON request body" +#swagger.deprecated = true +#swagger.summary = "Deprecated raw query search" #swagger.description = "Retrieves all matching CVE records after applying the JSON request body
+Retrieves all matching CVE records after applying the JSON request body. This endpoint is deprecated and can be disabled by the service operator. Use POST /search instead.
#swagger.requestBody = { required: true, content: { @@ -288,8 +439,23 @@ router.post( } #swagger.responses[200] = { description: 'A list of matching CVE records', +headers: { + "X-Request-ID": { + description: "Request correlation ID", + schema: { + type: "string" + } + } +} +} +#swagger.responses[504] = { +description: 'Search exceeded its OpenSearch request deadline', +} +#swagger.responses[410] = { +description: 'The deprecated endpoint is disabled; use POST /search', } */ + requireWebSearchEnabled, body().custom(hasOnlyWebSearchBodyFields).withMessage(error.webSearchUnsupportedFieldError()), body('query') .exists({ checkFalsy: true }) diff --git a/routes/index.test.js b/routes/index.test.js index 5102fc9..c1cd095 100644 --- a/routes/index.test.js +++ b/routes/index.test.js @@ -19,8 +19,25 @@ jest.mock('../controllers/searchController', () => ({ ) })); +jest.mock('../utils/featureFlags', () => ({ + isWebSearchEnabled: jest.fn(() => true) +})); + +const mockLimitSearchRequests = jest.fn((req, res, next) => next()); +const mockCheckOpenSearchReadiness = jest.fn(); + +jest.mock('../utils/searchRateLimiter', () => ({ + createSearchRateLimiter: jest.fn(() => mockLimitSearchRequests) +})); + +jest.mock('../utils/searchProvider', () => ({ + checkOpenSearchReadiness: mockCheckOpenSearchReadiness, + getOpenSearchReadinessTimeoutMs: jest.fn(() => 5000) +})); + const mw = require('../controllers/middleware'); const controller = require('../controllers/searchController'); +const featureFlags = require('../utils/featureFlags'); const router = require('./index'); describe('routes/index router', () => { @@ -29,6 +46,9 @@ describe('routes/index router', () => { beforeEach(() => { app = createRouterApp(router); jest.clearAllMocks(); + featureFlags.isWebSearchEnabled.mockReturnValue(true); + mockLimitSearchRequests.mockImplementation((req, res, next) => next()); + mockCheckOpenSearchReadiness.mockResolvedValue(true); }); test('GET /api/health-check returns 200', async () => { @@ -38,6 +58,35 @@ describe('routes/index router', () => { expect(response.status).toBe(200); expect(response.text).toBe(''); + expect(mockCheckOpenSearchReadiness).not.toHaveBeenCalled(); + }); + + test('GET /api/ready-check returns 200 when the configured OpenSearch index exists', async () => { + const response = await request(app, { + path: '/api/ready-check' + }); + + expect(response.status).toBe(200); + expect(response.text).toBe(''); + expect(mockCheckOpenSearchReadiness).toHaveBeenCalledWith({ + signal: expect.any(AbortSignal) + }); + }); + + test('GET /api/ready-check returns a sanitized 503 when OpenSearch is unavailable', async () => { + mockCheckOpenSearchReadiness.mockRejectedValue(new Error('sensitive internal OpenSearch details')); + + const response = await request(app, { + path: '/api/ready-check' + }); + + expect(response.status).toBe(503); + expect(response.headers['retry-after']).toBe('5'); + expect(response.body).toStrictEqual({ + error: 'SEARCH_NOT_READY', + message: 'Search service is not ready. Please try again later.' + }); + expect(response.text).not.toContain('sensitive internal OpenSearch details'); }); test('POST /search reaches middleware and controller for valid JSON body fields', async () => { @@ -51,6 +100,7 @@ describe('routes/index router', () => { }); expect(response.status).toBe(200); + expect(mockLimitSearchRequests).toHaveBeenCalledTimes(1); expect(mw.validateParams).toHaveBeenCalledTimes(1); expect(controller.SEARCH).toHaveBeenCalledTimes(1); expect(response.body).toStrictEqual({ @@ -62,6 +112,64 @@ describe('routes/index router', () => { }); }); + test('POST /search permits an omitted request body', async () => { + const response = await request(app, { + method: 'POST', + path: '/search' + }); + + expect(response.status).toBe(200); + expect(mockLimitSearchRequests).toHaveBeenCalledTimes(1); + expect(mw.validateParams).toHaveBeenCalledTimes(1); + expect(controller.SEARCH).toHaveBeenCalledTimes(1); + expect(response.body).toStrictEqual({ + route: 'search', + params: {} + }); + }); + + test('POST /search rejects a non-empty body with an unsupported media type before rate limiting', async () => { + const response = await request(app, { + method: 'POST', + path: '/search', + rawBody: '{"keywordSearch":"test"}', + headers: { + 'Content-Type': 'text/plain' + } + }); + + expect(response.status).toBe(415); + expect(response.body).toStrictEqual({ + error: 'SEARCH_CONTENT_TYPE', + message: 'The /search request body must use the application/json media type with UTF-8 encoding.' + }); + expect(mockLimitSearchRequests).not.toHaveBeenCalled(); + expect(mw.validateParams).not.toHaveBeenCalled(); + expect(controller.SEARCH).not.toHaveBeenCalled(); + }); + + test('POST /search returns a rate-limit response before validation and controller work', async () => { + mockLimitSearchRequests.mockImplementation((req, res) => + res.status(429).json({ + error: 'SEARCH_RATE_LIMITED', + message: 'Too many search requests. Please retry later.' + }) + ); + + const response = await request(app, { + method: 'POST', + path: '/search', + body: { + keywordSearch: 'test' + } + }); + + expect(response.status).toBe(429); + expect(response.body.error).toBe('SEARCH_RATE_LIMITED'); + expect(mw.validateParams).not.toHaveBeenCalled(); + expect(controller.SEARCH).not.toHaveBeenCalled(); + }); + test('GET /search is no longer mounted', async () => { const response = await request(app, { path: '/search?keywordSearch=test' @@ -224,6 +332,7 @@ describe('routes/index router', () => { }); expect(response.status).toBe(200); + expect(mockLimitSearchRequests).not.toHaveBeenCalled(); expect(controller.WEB_SEARCH).toHaveBeenCalledTimes(1); expect(response.body).toStrictEqual({ route: 'webSearch', @@ -239,6 +348,23 @@ describe('routes/index router', () => { }); }); + test('POST /webSearch returns 410 before validation and controller work when disabled', async () => { + featureFlags.isWebSearchEnabled.mockReturnValue(false); + + const response = await request(app, { + method: 'POST', + path: '/webSearch', + body: {} + }); + + expect(response.status).toBe(410); + expect(response.body).toStrictEqual({ + error: 'WEB_SEARCH_DISABLED', + message: 'The /webSearch endpoint is disabled. Use POST /search instead.' + }); + expect(controller.WEB_SEARCH).not.toHaveBeenCalled(); + }); + test('POST /webSearch trims query and normalizes integer body fields before the controller runs', async () => { const response = await request(app, { method: 'POST', diff --git a/routes/search.integration.test.js b/routes/search.integration.test.js index 9b671b4..5319044 100644 --- a/routes/search.integration.test.js +++ b/routes/search.integration.test.js @@ -1,15 +1,15 @@ const { request } = require('../test/http'); +const constants = require('../utils/constants'); -const mockApiSearch = jest.fn(); +const mockSearchOpenSearch = jest.fn(); const mockBasicSearch = jest.fn(); jest.mock('../utils/searchProvider', () => ({ - getAdvancedSearchManager: jest.fn(() => ({ - apiSearch: mockApiSearch - })), getBasicSearchManager: jest.fn(() => ({ search: mockBasicSearch })), + getSearchRequestDeadlineMs: jest.fn(() => 30000), + searchOpenSearch: mockSearchOpenSearch, shouldLogSearchPerformance: jest.fn(() => false) })); @@ -47,12 +47,12 @@ function basicResponse({ total = 0, hits = [] } = {}) { describe('search route integration', () => { beforeEach(() => { - mockApiSearch.mockReset(); + mockSearchOpenSearch.mockReset(); mockBasicSearch.mockReset(); }); test('POST /search validates, builds the OpenSearch request, and formats pagination metadata', async () => { - mockApiSearch.mockResolvedValue(advancedResponse(1, [{ _id: 'CVE-2024-0001' }])); + mockSearchOpenSearch.mockResolvedValue(advancedResponse(1, [{ _id: 'CVE-2024-0001' }]).data); const response = await request(createApp(), { method: 'POST', @@ -65,23 +65,31 @@ describe('search route integration', () => { }); expect(response.status).toBe(200); - expect(mockApiSearch).toHaveBeenCalledWith( - null, + expect(mockSearchOpenSearch).toHaveBeenCalledWith( { - filters: [], - rangeObjects: [], - resultsPerPage: 1, + query: { + bool: { + must: [ + { + simple_query_string: { + query: 'remote* code*', + default_operator: 'AND', + fields: ['containers.cna.descriptions.value'] + } + } + ], + filter: [] + } + }, + track_total_hits: true, + size: 1, from: 0 }, - [ - { - simple_query_string: { - query: 'remote* code*', - default_operator: 'AND', - fields: ['containers.cna.descriptions.value'] - } - } - ] + expect.objectContaining({ + operation: 'standard', + signal: expect.any(AbortSignal), + onStart: expect.any(Function) + }) ); expect(response.body.page).toBe(1); expect(response.body.pageSize).toBe(1); @@ -102,7 +110,48 @@ describe('search route integration', () => { error: 'DATE_TIME_FORMAT', message: "The pubStartDate parameter must match the following format: yyyy-MM-ddTHH:mm:ss.SSS'Z'" }); - expect(mockApiSearch).not.toHaveBeenCalled(); + expect(mockSearchOpenSearch).not.toHaveBeenCalled(); + }); + + test('POST /search rejects excessive keyword terms before invoking OpenSearch', async () => { + const response = await request(createApp(), { + method: 'POST', + path: '/search', + body: { + keywordSearch: Array(constants.MAX_KEYWORD_SEARCH_TOKENS + 1) + .fill('term') + .join(' ') + } + }); + + expect(response.status).toBe(400); + expect(response.body).toStrictEqual({ + error: 'SEARCH_FIELD_TOKEN_LIMIT', + message: `The keywordSearch field must contain ${constants.MAX_KEYWORD_SEARCH_TOKENS} or fewer terms.` + }); + expect(mockSearchOpenSearch).not.toHaveBeenCalled(); + }); + + test('POST /search rejects an inverted virtualMatchString version range before invoking OpenSearch', async () => { + const response = await request(createApp(), { + method: 'POST', + path: '/search', + body: { + virtualMatchString: 'cpe:2.3:o:linux:linux_kernel', + versionStart: '3.0', + versionStartType: 'including', + versionEnd: '2.0', + versionEndType: 'including' + } + }); + + expect(response.status).toBe(400); + expect(response.body).toStrictEqual({ + error: 'VERSION_RANGE_ORDER', + message: + 'The versionStart parameter must be less than versionEnd. Equal values require both bounds to be including.' + }); + expect(mockSearchOpenSearch).not.toHaveBeenCalled(); }); test('POST /webSearch normalizes body fields and formats BasicSearchManager responses', async () => { diff --git a/routes/swagger.js b/routes/swagger.js index ea6f64f..1cf65db 100644 --- a/routes/swagger.js +++ b/routes/swagger.js @@ -1,5 +1,6 @@ const path = require('path'); const swaggerAutogen = require('swagger-autogen'); +const packageInfo = require('../package.json'); const constants = require('../utils/constants'); const projectRoot = path.join(__dirname, '..'); @@ -15,9 +16,10 @@ function searchRequestProperty(description, schema) { const searchRequestProperties = { keywordSearch: searchRequestProperty( - "Returns CVE records where a word or phrase is included in the record's description. Multiple keywords operate like an AND statement, returning records whose descriptions include every keyword.", + `Returns CVE records where a word or phrase is included in the record's description. Multiple keywords operate like an AND statement, returning records whose descriptions include every keyword. Limited to ${constants.SEARCH_STRING_LENGTH_LIMITS.keywordSearch} characters and ${constants.MAX_KEYWORD_SEARCH_TOKENS} whitespace-delimited terms.`, { type: 'string', + maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.keywordSearch, example: 'remote code execution' } ), @@ -29,9 +31,10 @@ const searchRequestProperties = { } ), cveIds: searchRequestProperty( - `Returns one or more CVE records identified by a comma-separated list of CVE IDs. Limited to ${constants.MAX_CVE_IDS} CVE IDs.`, + `Returns one or more CVE records identified by a comma-separated list of CVE IDs. Limited to ${constants.MAX_CVE_IDS} CVE IDs, ${constants.MAX_CVE_ID_LENGTH} characters per ID, and ${constants.SEARCH_STRING_LENGTH_LIMITS.cveIds} characters overall.`, { type: 'string', + maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.cveIds, example: 'CVE-2024-1234,CVE-2024-5678' } ), @@ -41,6 +44,7 @@ const searchRequestProperties = { }), cweId: searchRequestProperty('Returns CVE records that include the provided Common Weakness Enumeration ID.', { type: 'string', + maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.cweId, example: 'CWE-79' }), cvssV2Severity: searchRequestProperty( @@ -118,19 +122,16 @@ const searchRequestProperties = { example: 'CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N' } ), - sourceIdentifier: searchRequestProperty('Returns CVE records that contain the provided source identifier.', { - type: 'string', - example: 'security@example.org' - }), cpeName: searchRequestProperty( - 'Returns CVE records whose CPE Match Criteria apply to the provided CPE 2.3 asset name. The part, vendor, product, and version components must contain values other than *. The concrete version can match stored criteria with the same version or criteria with a wildcard version and version range fields. Invalid cpeName values return HTTP 404. Use virtualMatchString for partial CPE Match String searches.', + 'Returns CVE records whose CPE Match Criteria apply to the provided CPE 2.3 asset name. CPE string literal comparisons are insensitive to lexical case. The part, vendor, product, and version components must contain values other than *. Every concrete non-version component must be compatible with the same CPE Match Criteria entry that satisfies the version and vulnerability checks; optional * components remain broad matches. The concrete version can match stored criteria with the same version or criteria with a wildcard version and version range fields. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. The version - represents the CPE logical value NA; numeric range boundaries on wildcard-version criteria do not exclude it. Invalid cpeName values return HTTP 404. Use virtualMatchString for partial CPE Match String searches.', { type: 'string', + maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.cpeName, example: 'cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*' } ), isVulnerable: searchRequestProperty( - 'When true, returns CVE records whose CPE Match Criteria match cpeName and are marked vulnerable. cpeName is required when isVulnerable is true. virtualMatchString cannot be provided with isVulnerable.', + 'When true, returns CVE records where the same CPE Match Criteria entry both matches cpeName and is marked vulnerable. False or omitted does not apply a vulnerability restriction. cpeName is required when isVulnerable is true. virtualMatchString cannot be provided with isVulnerable.', { type: 'boolean', default: false @@ -188,9 +189,10 @@ const searchRequestProperties = { } ), versionStart: searchRequestProperty( - 'Sets the starting version bound for a CPE Match String range search. versionStartType and virtualMatchString are required when versionStart is provided. virtualMatchString must not include a version component.', + 'Sets the starting version bound for a CPE Match String range search. versionStartType and virtualMatchString are required when versionStart is provided. When both bounds are provided, versionStart must be less than versionEnd; equal values require both bounds to be including. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. virtualMatchString must not include a version component.', { type: 'string', + maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.versionStart, example: '2.2' } ), @@ -202,9 +204,10 @@ const searchRequestProperties = { } ), versionEnd: searchRequestProperty( - 'Sets the ending version bound for a CPE Match String range search. versionEndType and virtualMatchString are required when versionEnd is provided. virtualMatchString must not include a version component.', + 'Sets the ending version bound for a CPE Match String range search. versionEndType and virtualMatchString are required when versionEnd is provided. When both bounds are provided, versionStart must be less than versionEnd; equal values require both bounds to be including. Numeric version segments in ranges ignore leading zeros; exact CPE Match Criteria preserve leading zeros. Version qualifiers use Maven-style ordering, with prerelease qualifiers before an unqualified release and sp or unknown qualifiers after it. virtualMatchString must not include a version component.', { type: 'string', + maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.versionEnd, example: '2.6' } ), @@ -216,9 +219,10 @@ const searchRequestProperties = { } ), virtualMatchString: searchRequestProperty( - 'Returns CVE records whose CPE Match Criteria are compatible with the provided CPE 2.3 match string. Omitted trailing components and * components act as broad matches. When used with versionStart or versionEnd, provide a partial CPE 2.3 match string without a version component.', + 'Returns CVE records whose CPE Match Criteria are compatible with the provided CPE 2.3 match string. CPE string literal comparisons are insensitive to lexical case. Omitted trailing components and * components act as broad matches. When used with versionStart or versionEnd, provide a partial CPE 2.3 match string without a version component.', { type: 'string', + maxLength: constants.SEARCH_STRING_LENGTH_LIMITS.virtualMatchString, example: 'cpe:2.3:o:microsoft:windows_10_1809' } ) @@ -227,7 +231,7 @@ const searchRequestProperties = { const doc = { openapi: '3.0.2', info: { - version: '0.3.0-beta', + version: packageInfo.version, title: 'CVE Search API', description: 'CVE Search API to retrieve all matching CVE records after applying request inputs as filters.', contact: { diff --git a/server.js b/server.js index da3cfd8..4c94a56 100644 --- a/server.js +++ b/server.js @@ -7,9 +7,13 @@ const http = require('http'); const logger = require('morgan'); const path = require('path'); const router = require('./routes/index'); +const errors = require('./utils/errors'); +const requestLogging = require('./utils/requestLogging'); dotenv.load(); +const error = new errors.errors(); + const swaggerSetupOptions = { swaggerOptions: { supportedSubmitMethods: [] @@ -22,31 +26,44 @@ const swaggerSetupOptions = { .swagger-ui .parameters-col_description input { display: none }` }; -function getRedactedRequestPath(req) { - return req.path || String(req.originalUrl || req.url || '-').split('?')[0]; -} - function createRequestLogger() { if (process.env.NODE_ENV === 'test') { return (_req, _res, next) => next(); } - logger.token('redacted-url', getRedactedRequestPath); - return logger(':method :redacted-url :status :res[content-length] - :response-time ms'); + logger.token('redacted-url', requestLogging.getRedactedRequestPath); + logger.token('request-id', (req) => req.requestId || '-'); + return logger(':method :redacted-url :status :res[content-length] - :response-time ms request-id=:request-id', { + skip: requestLogging.shouldSkipAccessLog + }); +} + +function handleRequestError(err, req, res, next) { + if (err?.type === 'entity.parse.failed') { + return res.status(400).json(error.malformedJsonError()); + } + + if (err?.status === 415 && req.path === '/search') { + return res.status(415).json(error.searchContentTypeError()); + } + + next(err); } function createApp() { const app = express(); + app.use(requestLogging.assignRequestId); app.use(createRequestLogger()); + app.use(cors()); app.use(express.static(path.join(__dirname, 'public'))); app.use(express.json()); - app.use(cors()); app.get('/api-docs/openapi.json', (req, res) => res.json(openApiSpecification)); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openApiSpecification, swaggerSetupOptions)); app.use('/', router); + app.use(handleRequestError); return app; } @@ -73,7 +90,7 @@ if (require.main === module) { module.exports = { createApp, createRequestLogger, - getRedactedRequestPath, + getRedactedRequestPath: requestLogging.getRedactedRequestPath, getRuntimeConfig, startServer, swaggerSetupOptions diff --git a/server.test.js b/server.test.js index 6ed4e57..51ce3c8 100644 --- a/server.test.js +++ b/server.test.js @@ -68,6 +68,18 @@ describe('server application bootstrap', () => { expect(response.status).toBe(200); expect(response.text).toBe(''); expect(response.headers['access-control-allow-origin']).toBe('*'); + expect(response.headers['x-request-id']).toMatch(/^[0-9a-f-]{36}$/); + }); + + test('createApp preserves valid caller request IDs in response headers', async () => { + const response = await request(createApp(), { + path: '/api/health-check', + headers: { + 'X-Request-ID': 'caller-request-123' + } + }); + + expect(response.headers['x-request-id']).toBe('caller-request-123'); }); test('createApp mounts the OpenAPI JSON endpoint', async () => { @@ -130,6 +142,44 @@ describe('server application bootstrap', () => { }); }); + test('createApp returns a sanitized error for malformed JSON', async () => { + const response = await request(createApp(), { + method: 'POST', + path: '/search', + rawBody: '{"keywordSearch":', + headers: { + 'Content-Type': 'application/json' + } + }); + + expect(response.status).toBe(400); + expect(response.body).toStrictEqual({ + error: 'MALFORMED_JSON', + message: 'The request body contains malformed JSON.' + }); + expect(response.headers['access-control-allow-origin']).toBe('*'); + expect(mockSearch).not.toHaveBeenCalled(); + }); + + test('createApp returns a sanitized error for unsupported JSON encoding', async () => { + const response = await request(createApp(), { + method: 'POST', + path: '/search', + rawBody: '{"keywordSearch":"apache"}', + headers: { + 'Content-Type': 'application/json; charset=iso-8859-1' + } + }); + + expect(response.status).toBe(415); + expect(response.body).toStrictEqual({ + error: 'SEARCH_CONTENT_TYPE', + message: 'The /search request body must use the application/json media type with UTF-8 encoding.' + }); + expect(response.headers['access-control-allow-origin']).toBe('*'); + expect(mockSearch).not.toHaveBeenCalled(); + }); + test('getRuntimeConfig preserves baseURL behavior when PORT is set without BASE_URL', () => { delete process.env.BASE_URL; process.env.PORT = '4567'; @@ -187,8 +237,12 @@ describe('server application bootstrap', () => { createRequestLogger(); expect(mockMorgan.token).toHaveBeenCalledWith('redacted-url', getRedactedRequestPath); + expect(mockMorgan.token).toHaveBeenCalledWith('request-id', expect.any(Function)); expect(mockMorgan).toHaveBeenCalledWith( - ':method :redacted-url :status :res[content-length] - :response-time ms' + ':method :redacted-url :status :res[content-length] - :response-time ms request-id=:request-id', + { + skip: expect.any(Function) + } ); }); diff --git a/test/artillery/.env.example b/test/artillery/.env.example deleted file mode 100644 index aab1d30..0000000 --- a/test/artillery/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -# Copy to test/artillery/.env for local use. Do not commit real URLs or keys. -CVE_SEARCH_API_DEV_URL= -CVE_SEARCH_API_TEST_URL= -CVE_SEARCH_API_INT_URL= -CVE_SEARCH_API_PROD_URL= -ARTILLERY_CLOUD_API_KEY= diff --git a/test/artillery/README.md b/test/artillery/README.md deleted file mode 100644 index 9ae00ce..0000000 --- a/test/artillery/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# Artillery API Checks - -These Artillery profiles exercise the live API surface. They require the API to be running and connected to an OpenSearch instance. - -Default target: `http://127.0.0.1:4000` - -Named environments: - -- `local`: `http://127.0.0.1:4000` -- `dev`: reads `CVE_SEARCH_API_DEV_URL` -- `test`: reads `CVE_SEARCH_API_TEST_URL` -- `int`: reads `CVE_SEARCH_API_INT_URL` -- `prod`: reads `CVE_SEARCH_API_PROD_URL` - -Override the target with Artillery's `--target` flag: - -```sh -npm run artillery:smoke -- --target http://127.0.0.1:4100 -npm run artillery:contract -- --target http://127.0.0.1:4100 -npm run artillery:load -- --target http://127.0.0.1:4100 -npm run artillery:stress -- --target http://127.0.0.1:4100 -``` - -Run against a named deployed environment: - -```sh -CVE_SEARCH_API_DEV_URL="$DEV_API_URL" npm run artillery:smoke -- --environment dev -``` - -You can also keep local target values in an untracked env file: - -```sh -cp test/artillery/.env.example test/artillery/.env -npm run artillery:smoke -- --dotenv test/artillery/.env --environment dev -``` - -Run public-search load or stress checks only after the target environment owner approves the traffic volume: - -```sh -CVE_SEARCH_API_TEST_URL="$TEST_API_URL" npm run artillery:load -- --environment test -CVE_SEARCH_API_INT_URL="$INT_API_URL" npm run artillery:stress -- --environment int -``` - -Record a run to Artillery Cloud by setting `ARTILLERY_CLOUD_API_KEY` outside the repository and passing `--record`: - -```sh -ARTILLERY_CLOUD_API_KEY=... \ -CVE_SEARCH_API_DEV_URL="$DEV_API_URL" \ -npm run artillery:smoke -- \ - --environment dev \ - --record \ - --name cve-search-api-smoke-dev \ - --tags service:cve-search-api,env:dev,profile:smoke -``` - -Use matching tags for load and stress runs so Artillery Cloud comparisons stay meaningful: - -```sh -ARTILLERY_CLOUD_API_KEY=... \ -CVE_SEARCH_API_INT_URL="$INT_API_URL" \ -npm run artillery:stress -- \ - --environment int \ - --record \ - --name cve-search-api-stress-int \ - --tags service:cve-search-api,env:int,profile:stress -``` - -If the command fails with `SELF_SIGNED_CERT_IN_CHAIN`, Node does not trust the certificate chain for Artillery Cloud or the HTTPS API target. Use the same host CA bundle used by local Docker builds. `Dockerfile.local` receives that file from `NPM_CA_CERT` and mounts it inside the build as `/run/secrets/npm_ca`; Artillery needs the host path through `NODE_EXTRA_CA_CERTS`: - -```sh -export NODE_EXTRA_CA_CERTS="$NPM_CA_CERT" -``` - -Then rerun the Artillery command. Keep Artillery Cloud keys, deployment URLs, and certificate file paths in local environment variables or an untracked env file. - -Profiles: - -- `smoke.yml`: one virtual user runs the expected successful paths for health, OpenAPI docs, `POST /search`, and `POST /webSearch`. -- `contract.yml`: one virtual user runs negative request-shape checks for validation and removed endpoint behavior. -- `load.yml`: a conservative weighted traffic mix for short local performance checks. -- `stress.yml`: a public-search weighted traffic mix with ramping arrival rates for approved capacity tests. - -Each profile records endpoint-level metrics under stable request names and enforces basic SLO gates: - -- `smoke.yml`: p95 response time under 5000 ms and zero virtual user errors. -- `contract.yml`: p95 response time under 5000 ms and zero virtual user errors. -- `load.yml`: p95 response time under 3000 ms, p99 response time under 8000 ms, and virtual user error rate at or below 1%. -- `stress.yml`: p95 response time under 8000 ms, p99 response time under 15000 ms, and virtual user error rate at or below 5%. - -Use `load.yml` or `stress.yml` against shared or production-like environments only when the target environment owner has approved the traffic volume. The stress profile includes the expensive `POST /search virtualMatchString range` path at low weight so capacity reports show whether public traffic amplifies that query shape. - -When diagnosing slow `/search` requests during a controlled run, set `SearchLogPerformanceTimings=true` on the API deployment before starting the profile. Leave it disabled for normal operation unless the additional structured logs are expected and acceptable. - -These profiles are intentionally separate from `npm run quality` because they depend on live infrastructure and real OpenSearch data. - -See the root README's `Interpreting Artillery Results` section for how to read expectation failures, SLO failures, latency percentiles, virtual user counters, endpoint-level metrics, and Cloud run tags. diff --git a/test/artillery/contract.yml b/test/artillery/contract.yml deleted file mode 100644 index 65d39fd..0000000 --- a/test/artillery/contract.yml +++ /dev/null @@ -1,138 +0,0 @@ -config: - target: 'http://127.0.0.1:4000' - environments: - local: - target: 'http://127.0.0.1:4000' - dev: - target: '{{ $env.CVE_SEARCH_API_DEV_URL }}' - test: - target: '{{ $env.CVE_SEARCH_API_TEST_URL }}' - int: - target: '{{ $env.CVE_SEARCH_API_INT_URL }}' - prod: - target: '{{ $env.CVE_SEARCH_API_PROD_URL }}' - phases: - - duration: 1 - arrivalCount: 1 - name: 'single-user contract' - plugins: - metrics-by-endpoint: - stripQueryString: true - useOnlyRequestNames: true - metricsNamespace: 'cve-search-api.endpoint' - expect: - reportFailuresAsErrors: true - outputFormat: 'prettyError' - ensure: - p95: 5000 - maxErrorRate: 0 - http: - defaults: - headers: - Accept: 'application/json' - Content-Type: 'application/json' - -scenarios: - - name: 'API validation contract' - flow: - - get: - name: 'GET /search removed endpoint' - url: '/search?keywordSearch=apache' - expect: - - statusCode: 404 - - - post: - name: 'POST /search rejects query string' - url: '/search?keywordSearch=apache' - json: {} - expect: - - statusCode: 400 - - contentType: 'json' - - - post: - name: 'POST /search rejects invalid date' - url: '/search' - json: - pubStartDate: '2024-02-30T00:00:00.000Z' - expect: - - statusCode: 400 - - contentType: 'json' - - - post: - name: 'POST /search rejects invalid cveIds' - url: '/search' - json: - cveIds: 'not-a-cve' - expect: - - statusCode: 400 - - contentType: 'json' - - - post: - name: 'POST /search rejects deprecated cveId' - url: '/search' - json: - cveId: 'CVE-2024-0001' - expect: - - statusCode: 400 - - contentType: 'json' - - - post: - name: 'POST /search rejects invalid cpeName' - url: '/search' - json: - cpeName: 'cpe:2.3:a:freetype:freetype:*:*:*:*:*:*:*:*' - expect: - - statusCode: 404 - - contentType: 'json' - - - post: - name: 'POST /search rejects range without virtualMatchString' - url: '/search' - json: - versionStart: '2.2' - versionStartType: 'including' - expect: - - statusCode: 400 - - contentType: 'json' - - - post: - name: 'POST /search rejects virtualMatchString version component' - url: '/search' - json: - virtualMatchString: 'cpe:2.3:o:linux:linux_kernel:*' - versionStart: '2.2' - versionStartType: 'including' - expect: - - statusCode: 400 - - contentType: 'json' - - - post: - name: 'POST /webSearch rejects blank query' - url: '/webSearch' - json: - query: '' - expect: - - statusCode: 400 - - contentType: 'json' - - - post: - name: 'POST /webSearch rejects unsupported sort' - url: '/webSearch' - json: - query: 'apache' - sort: - property: 'published' - order: 'desc' - expect: - - statusCode: 400 - - contentType: 'json' - - - post: - name: 'POST /webSearch rejects unsupported field' - url: '/webSearch' - json: - query: 'apache' - unsupported: true - expect: - - statusCode: 400 - - contentType: 'json' diff --git a/test/artillery/load.yml b/test/artillery/load.yml deleted file mode 100644 index b5e127e..0000000 --- a/test/artillery/load.yml +++ /dev/null @@ -1,141 +0,0 @@ -config: - target: 'http://127.0.0.1:4000' - environments: - local: - target: 'http://127.0.0.1:4000' - dev: - target: '{{ $env.CVE_SEARCH_API_DEV_URL }}' - test: - target: '{{ $env.CVE_SEARCH_API_TEST_URL }}' - int: - target: '{{ $env.CVE_SEARCH_API_INT_URL }}' - prod: - target: '{{ $env.CVE_SEARCH_API_PROD_URL }}' - phases: - - duration: 30 - arrivalRate: 1 - rampTo: 3 - maxVusers: 10 - name: 'warm up' - - duration: 60 - arrivalRate: 3 - maxVusers: 15 - name: 'sustain' - plugins: - metrics-by-endpoint: - stripQueryString: true - useOnlyRequestNames: true - metricsNamespace: 'cve-search-api.endpoint' - expect: - reportFailuresAsErrors: true - outputFormat: 'silent' - ensure: - p95: 3000 - p99: 8000 - maxErrorRate: 1 - http: - defaults: - headers: - Accept: 'application/json' - Content-Type: 'application/json' - -scenarios: - - name: 'health check' - weight: 10 - flow: - - get: - name: 'GET /api/health-check' - url: '/api/health-check' - expect: - - statusCode: 200 - - - name: 'keyword search' - weight: 35 - flow: - - post: - name: 'POST /search keyword' - url: '/search' - json: - keywordSearch: 'apache' - resultsPerPage: 5 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'multiple CVE IDs' - weight: 15 - flow: - - post: - name: 'POST /search cveIds' - url: '/search' - json: - cveIds: 'CVE-2024-0001,CVE-2024-0002' - resultsPerPage: 5 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'exclude rejected CVEs' - weight: 15 - flow: - - post: - name: 'POST /search noRejected' - url: '/search' - json: - noRejected: true - resultsPerPage: 5 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'web search' - weight: 15 - flow: - - post: - name: 'POST /webSearch keyword' - url: '/webSearch' - json: - query: 'apache' - sort: - property: 'cveId' - order: 'asc' - from: 0 - size: 5 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'CPE name search' - weight: 5 - flow: - - post: - name: 'POST /search cpeName' - url: '/search' - json: - cpeName: 'cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*' - resultsPerPage: 5 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'virtual match string range' - weight: 5 - flow: - - post: - name: 'POST /search virtualMatchString range' - url: '/search' - json: - virtualMatchString: 'cpe:2.3:o:linux:linux_kernel' - versionStart: '2.2' - versionStartType: 'including' - versionEnd: '2.6' - versionEndType: 'excluding' - resultsPerPage: 5 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' diff --git a/test/artillery/smoke.yml b/test/artillery/smoke.yml deleted file mode 100644 index 364fe5f..0000000 --- a/test/artillery/smoke.yml +++ /dev/null @@ -1,128 +0,0 @@ -config: - target: 'http://127.0.0.1:4000' - environments: - local: - target: 'http://127.0.0.1:4000' - dev: - target: '{{ $env.CVE_SEARCH_API_DEV_URL }}' - test: - target: '{{ $env.CVE_SEARCH_API_TEST_URL }}' - int: - target: '{{ $env.CVE_SEARCH_API_INT_URL }}' - prod: - target: '{{ $env.CVE_SEARCH_API_PROD_URL }}' - phases: - - duration: 1 - arrivalCount: 1 - name: 'single-user smoke' - plugins: - metrics-by-endpoint: - stripQueryString: true - useOnlyRequestNames: true - metricsNamespace: 'cve-search-api.endpoint' - expect: - reportFailuresAsErrors: true - outputFormat: 'prettyError' - ensure: - p95: 5000 - maxErrorRate: 0 - http: - defaults: - headers: - Accept: 'application/json' - Content-Type: 'application/json' - -scenarios: - - name: 'API smoke checks' - flow: - - get: - name: 'GET /api/health-check' - url: '/api/health-check' - expect: - - statusCode: 200 - - - get: - name: 'GET /api-docs/openapi.json' - url: '/api-docs/openapi.json' - expect: - - statusCode: 200 - - contentType: 'json' - - - post: - name: 'POST /search keyword' - url: '/search' - json: - keywordSearch: 'apache' - resultsPerPage: 1 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - hasProperty: 'data' - - - post: - name: 'POST /search cveIds' - url: '/search' - json: - cveIds: 'CVE-2024-0001,CVE-2024-0002' - resultsPerPage: 2 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - hasProperty: 'data' - - - post: - name: 'POST /search noRejected' - url: '/search' - json: - noRejected: true - resultsPerPage: 1 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - hasProperty: 'data' - - - post: - name: 'POST /search cpeName' - url: '/search' - json: - cpeName: 'cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*' - resultsPerPage: 1 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - hasProperty: 'data' - - - post: - name: 'POST /search virtualMatchString range' - url: '/search' - json: - virtualMatchString: 'cpe:2.3:o:linux:linux_kernel' - versionStart: '2.2' - versionStartType: 'including' - versionEnd: '2.6' - versionEndType: 'excluding' - resultsPerPage: 1 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - hasProperty: 'data' - - - post: - name: 'POST /webSearch keyword' - url: '/webSearch' - json: - query: 'apache' - sort: - property: 'cveId' - order: 'asc' - from: 0 - size: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - hasProperty: 'data' diff --git a/test/artillery/stress.yml b/test/artillery/stress.yml deleted file mode 100644 index 997635d..0000000 --- a/test/artillery/stress.yml +++ /dev/null @@ -1,151 +0,0 @@ -config: - target: 'http://127.0.0.1:4000' - environments: - local: - target: 'http://127.0.0.1:4000' - dev: - target: '{{ $env.CVE_SEARCH_API_DEV_URL }}' - test: - target: '{{ $env.CVE_SEARCH_API_TEST_URL }}' - int: - target: '{{ $env.CVE_SEARCH_API_INT_URL }}' - prod: - target: '{{ $env.CVE_SEARCH_API_PROD_URL }}' - phases: - - duration: 60 - arrivalRate: 2 - rampTo: 10 - maxVusers: 60 - name: 'ramp to public traffic' - - duration: 120 - arrivalRate: 10 - maxVusers: 120 - name: 'sustain public traffic' - - duration: 60 - arrivalRate: 10 - rampTo: 25 - maxVusers: 180 - name: 'stress ramp' - - duration: 60 - arrivalRate: 25 - maxVusers: 220 - name: 'stress sustain' - plugins: - metrics-by-endpoint: - stripQueryString: true - useOnlyRequestNames: true - metricsNamespace: 'cve-search-api.endpoint' - expect: - reportFailuresAsErrors: true - outputFormat: 'silent' - ensure: - p95: 8000 - p99: 15000 - maxErrorRate: 5 - http: - timeout: 20 - defaults: - headers: - Accept: 'application/json' - Content-Type: 'application/json' - -scenarios: - - name: 'health check' - weight: 5 - flow: - - get: - name: 'GET /api/health-check' - url: '/api/health-check' - expect: - - statusCode: 200 - - - name: 'website keyword search' - weight: 35 - flow: - - post: - name: 'POST /search keyword' - url: '/search' - json: - keywordSearch: 'apache' - resultsPerPage: 10 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'website webSearch' - weight: 25 - flow: - - post: - name: 'POST /webSearch keyword' - url: '/webSearch' - json: - query: 'apache' - sort: - property: 'cveId' - order: 'asc' - from: 0 - size: 10 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'multiple CVE IDs' - weight: 15 - flow: - - post: - name: 'POST /search cveIds' - url: '/search' - json: - cveIds: 'CVE-2024-0001,CVE-2024-0002' - resultsPerPage: 2 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'exclude rejected CVEs' - weight: 10 - flow: - - post: - name: 'POST /search noRejected' - url: '/search' - json: - noRejected: true - resultsPerPage: 10 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'CPE name search' - weight: 5 - flow: - - post: - name: 'POST /search cpeName' - url: '/search' - json: - cpeName: 'cpe:2.3:a:freetype:freetype:2.8.1:*:*:*:*:*:*:*' - resultsPerPage: 5 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' - - - name: 'expensive virtual match string range' - weight: 5 - flow: - - post: - name: 'POST /search virtualMatchString range' - url: '/search' - json: - virtualMatchString: 'cpe:2.3:o:linux:linux_kernel' - versionStart: '2.2' - versionStartType: 'including' - versionEnd: '2.6' - versionEndType: 'excluding' - resultsPerPage: 5 - pageNumber: 1 - expect: - - statusCode: 200 - - contentType: 'json' diff --git a/test/http.js b/test/http.js index af737ec..716afe9 100644 --- a/test/http.js +++ b/test/http.js @@ -8,10 +8,24 @@ function createRouterApp(router) { return app; } -function request(app, { method = 'GET', path, body }) { +function hasHeader(headers, name) { + return Object.keys(headers).some((header) => header.toLowerCase() === name.toLowerCase()); +} + +function request(app, { method = 'GET', path, body, rawBody, headers = {} }) { return new Promise((resolve, reject) => { const server = app.listen(0, () => { - const payload = body ? JSON.stringify(body) : undefined; + const payload = + rawBody !== undefined ? String(rawBody) : body !== undefined ? JSON.stringify(body) : undefined; + const requestHeaders = { ...headers }; + if (payload !== undefined) { + if (rawBody === undefined && !hasHeader(requestHeaders, 'content-type')) { + requestHeaders['Content-Type'] = 'application/json'; + } + if (!hasHeader(requestHeaders, 'content-length')) { + requestHeaders['Content-Length'] = Buffer.byteLength(payload); + } + } const { port } = server.address(); const req = http.request( { @@ -19,12 +33,7 @@ function request(app, { method = 'GET', path, body }) { port, path, method, - headers: payload - ? { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(payload) - } - : {} + headers: requestHeaders }, (res) => { let text = ''; @@ -58,7 +67,7 @@ function request(app, { method = 'GET', path, body }) { server.close(() => reject(err)); }); - if (payload) { + if (payload !== undefined) { req.write(payload); } diff --git a/utils/constants.js b/utils/constants.js index b6c7696..85ae9f3 100644 --- a/utils/constants.js +++ b/utils/constants.js @@ -5,10 +5,25 @@ const MAX_CVSS_METRICS_LENGTH = 256; const MAX_CVSS_METRIC_TOKENS = 32; const MAX_CVSS_METRIC_TOKEN_LENGTH = 32; const MAX_CVE_IDS = 100; +const MAX_CVE_ID_LENGTH = 64; +const MAX_KEYWORD_SEARCH_TOKENS = 64; +const SEARCH_STRING_LENGTH_LIMITS = Object.freeze({ + keywordSearch: 512, + cveIds: 4096, + cweId: 32, + cpeName: 2048, + versionStart: 256, + versionEnd: 256, + virtualMatchString: 2048 +}); const SEARCH_POST_FILTER_CACHE_TTL_MS = 60 * 1000; const SEARCH_POST_FILTER_CACHE_MAX_ENTRIES = 100; const SEARCH_POST_FILTER_COLD_SEARCH_CONCURRENCY = 10; const SEARCH_POST_FILTER_COLD_SEARCH_MAX_QUEUE_DEPTH = 500; +const SEARCH_POST_FILTER_COLD_SEARCH_MAX_WAIT_MS = 10 * 1000; +const SEARCH_OPEN_SEARCH_REQUEST_TIMEOUT_MS = 20 * 1000; +const SEARCH_REQUEST_DEADLINE_MS = 30 * 1000; +const SEARCH_READINESS_TIMEOUT_MS = 5 * 1000; const rejectedCveStates = ['REJECT', 'REJECTED', 'Rejected']; const rejectedCveStateFields = ['cveMetadata.state', 'containers.cna.x_legacyV4Record.CVE_data_meta.STATE']; @@ -40,7 +55,6 @@ const cpeMatchFields = { * cvssV3_0Metrics: CVSSv3_0 vector string * cvssV3_1Metrics: CVSSv3_1 vector string * cvssV4Metrics: CVSSv4 vector string - * sourceIdentifier: string * cpeName: string, name of CPE to search for * isVulnerable: boolean * versionStart: string, lower version bound used with virtualMatchString @@ -63,7 +77,6 @@ const fields = { cvssV3Metrics: ['containers.cna.metrics.cvssV3_0.vectorString', 'containers.cna.metrics.cvssV3_1.vectorString'], cvssV4Severity: 'containers.cna.metrics.cvssV4_0.baseSeverity', cvssV4Metrics: 'containers.cna.metrics.cvssV4_0.vectorString', - sourceIdentifier: 'containers.cna.source.discovery', cpeName: cpeMatchFields.criteria, isVulnerable: cpeMatchFields.vulnerable, virtualMatchString: cpeMatchFields.criteria @@ -99,7 +112,6 @@ const searchQueryParams = [ 'cvssV3_0Metrics', 'cvssV3_1Metrics', 'cvssV4Metrics', - 'sourceIdentifier', 'cpeName', 'isVulnerable', 'lastModStartDate', @@ -122,10 +134,17 @@ module.exports = { MAX_CVSS_METRIC_TOKENS, MAX_CVSS_METRIC_TOKEN_LENGTH, MAX_CVE_IDS, + MAX_CVE_ID_LENGTH, + MAX_KEYWORD_SEARCH_TOKENS, + SEARCH_STRING_LENGTH_LIMITS, SEARCH_POST_FILTER_CACHE_TTL_MS, SEARCH_POST_FILTER_CACHE_MAX_ENTRIES, SEARCH_POST_FILTER_COLD_SEARCH_CONCURRENCY, SEARCH_POST_FILTER_COLD_SEARCH_MAX_QUEUE_DEPTH, + SEARCH_POST_FILTER_COLD_SEARCH_MAX_WAIT_MS, + SEARCH_OPEN_SEARCH_REQUEST_TIMEOUT_MS, + SEARCH_REQUEST_DEADLINE_MS, + SEARCH_READINESS_TIMEOUT_MS, RESULTS_PER_PAGE, REJECTED_CVE_STATES: rejectedCveStates, CPE_VERSION_RANGE_TYPES: cpeVersionRangeTypes, diff --git a/utils/cpe.js b/utils/cpe.js index c4f3bc5..7301566 100644 --- a/utils/cpe.js +++ b/utils/cpe.js @@ -1,6 +1,17 @@ const CPE_23_PREFIX = ['cpe', '2.3']; const CPE_COMPONENT_COUNT = 11; +const CPE_NOT_APPLICABLE = '-'; const REQUIRED_CPE_COMPONENT_INDEXES = [0, 1, 2, 3]; +const VERSION_QUALIFIERS = ['alpha', 'beta', 'milestone', 'rc', 'snapshot', '', 'sp']; +const VERSION_QUALIFIER_ALIASES = { + a: 'alpha', + b: 'beta', + cr: 'rc', + final: '', + ga: '', + m: 'milestone', + release: '' +}; function parseCpe23(input) { const value = String(input ?? '') @@ -231,6 +242,33 @@ function compareNumericTokens(a, b) { return normalizedA > normalizedB ? 1 : -1; } +function normalizeVersionQualifier(token) { + return Object.prototype.hasOwnProperty.call(VERSION_QUALIFIER_ALIASES, token) + ? VERSION_QUALIFIER_ALIASES[token] + : token; +} + +function compareVersionQualifiers(a, b) { + const normalizedA = normalizeVersionQualifier(a); + const normalizedB = normalizeVersionQualifier(b); + const rankA = VERSION_QUALIFIERS.indexOf(normalizedA); + const rankB = VERSION_QUALIFIERS.indexOf(normalizedB); + + if (rankA !== -1 || rankB !== -1) { + if (rankA === -1) return 1; + if (rankB === -1) return -1; + return Math.sign(rankA - rankB); + } + + if (normalizedA === normalizedB) return 0; + return normalizedA > normalizedB ? 1 : -1; +} + +function compareVersionTokenToRelease(token) { + if (/^\d+$/.test(token)) return /^0+$/.test(token) ? 0 : 1; + return compareVersionQualifiers(token, ''); +} + function compareCpeVersions(a, b) { const tokensA = tokenizeVersion(a); const tokensB = tokenizeVersion(b); @@ -245,10 +283,14 @@ function compareCpeVersions(a, b) { const tokenB = tokensB[i]; if (tokenA === undefined) { - return /^0+$/.test(tokenB) ? 0 : -1; + const result = compareVersionTokenToRelease(tokenB); + if (result === 0) continue; + return -result; } if (tokenB === undefined) { - return /^0+$/.test(tokenA) ? 0 : 1; + const result = compareVersionTokenToRelease(tokenA); + if (result === 0) continue; + return result; } if (tokenA === tokenB) continue; @@ -265,14 +307,19 @@ function compareCpeVersions(a, b) { return aIsNumber ? 1 : -1; } - return tokenA > tokenB ? 1 : -1; + const result = compareVersionQualifiers(tokenA, tokenB); + if (result !== 0) return result; } return 0; } +function cpeLiteralsEqual(left, right) { + return String(left).toLowerCase() === String(right).toLowerCase(); +} + function componentMatches(assetComponent, criteriaComponent) { - return criteriaComponent === '*' || criteriaComponent === assetComponent; + return assetComponent === '*' || criteriaComponent === '*' || cpeLiteralsEqual(criteriaComponent, assetComponent); } function hasBoundary(value) { @@ -334,6 +381,12 @@ function versionRangesOverlap(a, b) { } function rangeBoundaryMatches(assetVersion, cpeMatch) { + if (assetVersion === CPE_NOT_APPLICABLE) { + return ( + cpeMatch.versionStartExcluding !== CPE_NOT_APPLICABLE && cpeMatch.versionEndExcluding !== CPE_NOT_APPLICABLE + ); + } + if ( hasBoundary(cpeMatch.versionStartIncluding) && compareCpeVersions(assetVersion, cpeMatch.versionStartIncluding) < 0 @@ -365,29 +418,28 @@ function rangeBoundaryMatches(assetVersion, cpeMatch) { return true; } -function cpeMatchAppliesToCpeName(cpeMatch, cpeName, context) { +function cpeMatchAppliesToCpeName(cpeMatch, cpeName, context, { requireVulnerable = false } = {}) { const asset = cpeName?.valid ? cpeName : parseCpeName(cpeName); const criteria = getParsedCpeMatchCriteria(cpeMatch?.criteria, context); if (!asset.valid || !criteria.valid) return false; + if (requireVulnerable && cpeMatch?.vulnerable !== true) return false; + + for (let index = 0; index < asset.components.length; index++) { + if (index === 3) continue; - for (const index of [0, 1, 2]) { if (!componentMatches(asset.components[index], criteria.components[index])) { return false; } } if (criteria.version !== '*') { - return criteria.version === asset.version; + return cpeLiteralsEqual(criteria.version, asset.version); } return rangeBoundaryMatches(asset.version, cpeMatch); } -function componentMatchesVirtualMatchString(matchComponent, criteriaComponent) { - return matchComponent === '*' || criteriaComponent === '*' || criteriaComponent === matchComponent; -} - function cpeMatchOverlapsVirtualMatchStringRange(cpeMatch, virtualMatchStringRange, context) { const matchString = virtualMatchStringRange?.matchString?.valid ? virtualMatchStringRange.matchString @@ -399,7 +451,7 @@ function cpeMatchOverlapsVirtualMatchStringRange(cpeMatch, virtualMatchStringRan for (let index = 0; index < matchString.components.length; index++) { if (index === 3) continue; - if (!componentMatchesVirtualMatchString(matchString.components[index], criteria.components[index])) { + if (!componentMatches(matchString.components[index], criteria.components[index])) { return false; } } @@ -422,7 +474,7 @@ function cpeMatchMatchesVirtualMatchString(cpeMatch, virtualMatchString, context if (!matchString.valid || !criteria.valid) return false; for (let index = 0; index < matchString.components.length; index++) { - if (!componentMatchesVirtualMatchString(matchString.components[index], criteria.components[index])) { + if (!componentMatches(matchString.components[index], criteria.components[index])) { return false; } } @@ -453,8 +505,8 @@ function getCpeMatchesFromHit(hit) { return matches; } -function hitAppliesToCpeName(hit, cpeName, context) { - return getCpeMatchesFromHit(hit).some((match) => cpeMatchAppliesToCpeName(match, cpeName, context)); +function hitAppliesToCpeName(hit, cpeName, context, options) { + return getCpeMatchesFromHit(hit).some((match) => cpeMatchAppliesToCpeName(match, cpeName, context, options)); } function hitOverlapsVirtualMatchStringRange(hit, virtualMatchStringRange, context) { diff --git a/utils/cpe.test.js b/utils/cpe.test.js index 5344929..916a0d9 100644 --- a/utils/cpe.test.js +++ b/utils/cpe.test.js @@ -112,7 +112,7 @@ describe('CPE helpers', () => { ); }); - test('buildVirtualMatchStringCriteriaRegexp builds an NVD-style partial criteria pattern', () => { + test('buildVirtualMatchStringCriteriaRegexp builds a partial criteria pattern', () => { const parsed = cpe.parseVirtualMatchString('cpe:2.3:o:microsoft:windows_10_1809'); expect(cpe.buildVirtualMatchStringCriteriaRegexp(parsed)).toBe( @@ -156,12 +156,50 @@ describe('CPE helpers', () => { expect(cpe.buildCompatibleCriteriaPrefixes(parsed, 3)[0]).toBe('cpe:2.3:a:vendor:product\\:edition:'); }); - test('compareCpeVersions compares numeric version segments semantically', () => { + test('compareCpeVersions compares numeric version segments semantically for range ordering', () => { expect(cpe.compareCpeVersions('2.10', '2.9')).toBeGreaterThan(0); expect(cpe.compareCpeVersions('10.05.1', '10.5.1')).toBe(0); + expect(cpe.compareCpeVersions('10.5.0', '10.05.1')).toBeLessThan(0); + expect(cpe.compareCpeVersions('6.1', '6.02')).toBeLessThan(0); + expect(cpe.compareCpeVersions('6.2', '6.02')).toBe(0); + expect(cpe.compareCpeVersions('1', '1.0')).toBe(0); + expect(cpe.compareCpeVersions('1.0.0', '1')).toBe(0); + expect(cpe.compareCpeVersions('1', '1.0.1')).toBeLessThan(0); + expect(cpe.compareCpeVersions('1.0.1', '1')).toBeGreaterThan(0); expect(cpe.compareCpeVersions('1.0', '1.0.1')).toBeLessThan(0); }); + test('compareCpeVersions orders Maven-style qualifiers around an unqualified release', () => { + const orderedVersions = [ + '1.0.0-alpha1', + '1.0.0-alpha2', + '1.0.0-beta1', + '1.0.0-milestone1', + '1.0.0-rc1', + '1.0.0-snapshot', + '1.0.0', + '1.0.0-sp1', + '1.0.0-hf1', + '1.0.0-zz1' + ]; + + for (let index = 1; index < orderedVersions.length; index++) { + expect(cpe.compareCpeVersions(orderedVersions[index - 1], orderedVersions[index])).toBeLessThan(0); + } + }); + + test.each([ + ['1.0.0-a1', '1.0.0-alpha1'], + ['1.0.0-b1', '1.0.0-beta1'], + ['1.0.0-m1', '1.0.0-milestone1'], + ['1.0.0-cr1', '1.0.0-rc1'], + ['1.0.0-GA', '1.0.0'], + ['1.0.0-final', '1.0.0'], + ['1.0.0-release', '1.0.0'] + ])('compareCpeVersions treats %s and %s as equivalent qualifier forms', (left, right) => { + expect(cpe.compareCpeVersions(left, right)).toBe(0); + }); + test('cpeMatchAppliesToCpeName honors inclusive and exclusive version boundaries', () => { const cpeName = 'cpe:2.3:a:vendor:product:2.10:*:*:*:*:*:*:*'; @@ -187,6 +225,229 @@ describe('CPE helpers', () => { ).toBe(false); }); + test('cpeMatchAppliesToCpeName ignores leading zeros only when evaluating range boundaries', () => { + const ghostscriptRange = { + criteria: 'cpe:2.3:a:artifex:ghostscript:*:*:*:*:*:*:*:*', + versionEndExcluding: '10.05.1' + }; + const buffaloRange = { + criteria: 'cpe:2.3:o:buffalo:wsr3600be4-kh_firmware:*:*:*:*:*:*:*:*', + versionEndExcluding: '6.02' + }; + + expect( + cpe.cpeMatchAppliesToCpeName(ghostscriptRange, 'cpe:2.3:a:artifex:ghostscript:10.5.0:*:*:*:*:*:*:*') + ).toBe(true); + expect( + cpe.cpeMatchAppliesToCpeName(ghostscriptRange, 'cpe:2.3:a:artifex:ghostscript:10.5.1:*:*:*:*:*:*:*') + ).toBe(false); + expect( + cpe.cpeMatchAppliesToCpeName(buffaloRange, 'cpe:2.3:o:buffalo:wsr3600be4-kh_firmware:6.1:*:*:*:*:*:*:*') + ).toBe(true); + expect( + cpe.cpeMatchAppliesToCpeName(buffaloRange, 'cpe:2.3:o:buffalo:wsr3600be4-kh_firmware:6.2:*:*:*:*:*:*:*') + ).toBe(false); + }); + + test('cpeMatchAppliesToCpeName preserves leading zeros for exact criteria versions', () => { + const exactCriteria = { + criteria: 'cpe:2.3:o:linksys:re6250_firmware:1.0.04.001:*:*:*:*:*:*:*' + }; + + expect( + cpe.cpeMatchAppliesToCpeName(exactCriteria, 'cpe:2.3:o:linksys:re6250_firmware:1.0.04.001:*:*:*:*:*:*:*') + ).toBe(true); + expect( + cpe.cpeMatchAppliesToCpeName(exactCriteria, 'cpe:2.3:o:linksys:re6250_firmware:1.0.4.1:*:*:*:*:*:*:*') + ).toBe(false); + }); + + test('cpeMatchAppliesToCpeName treats prerelease qualifiers as earlier than the unqualified release', () => { + const criteria = { + criteria: 'cpe:2.3:a:vendor:product:*:*:*:*:*:*:*:*', + versionStartIncluding: '1.0.0-rc1', + versionEndExcluding: '1.0.0-sp1' + }; + + expect(cpe.cpeMatchAppliesToCpeName(criteria, 'cpe:2.3:a:vendor:product:1.0.0:*:*:*:*:*:*:*')).toBe(true); + expect(cpe.cpeMatchAppliesToCpeName(criteria, 'cpe:2.3:a:vendor:product:1.0.0-beta1:*:*:*:*:*:*:*')).toBe( + false + ); + }); + + test('cpeMatchAppliesToCpeName requires every concrete non-version component to match', () => { + const cpeName = 'cpe:2.3:a:palo_alto_networks:globalprotect_app:6.1.1:*:macos:*:*:*:*:*'; + const androidRange = { + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:android:*:*:*:*:*', + vulnerable: true, + versionStartIncluding: '6.1', + versionEndExcluding: '6.1.13' + }; + const macosRange = { + ...androidRange, + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:macos:*:*:*:*:*' + }; + + expect(cpe.cpeMatchAppliesToCpeName(androidRange, cpeName, undefined, { requireVulnerable: true })).toBe(false); + expect(cpe.cpeMatchAppliesToCpeName(macosRange, cpeName, undefined, { requireVulnerable: true })).toBe(true); + }); + + test('cpeMatchAppliesToCpeName compares concrete string literals without lexical case', () => { + const cpeMatch = { + criteria: 'cpe:2.3:a:Palo_Alto_Networks:GlobalProtect_App:*:*:*:*:*:macOS:*:*', + vulnerable: true, + versionStartIncluding: '6.0', + versionEndExcluding: '6.1' + }; + + expect( + cpe.cpeMatchAppliesToCpeName( + cpeMatch, + 'cpe:2.3:a:palo_alto_networks:globalprotect_app:6.0.1:*:*:*:*:macos:*:*', + undefined, + { requireVulnerable: true } + ) + ).toBe(true); + }); + + test('cpeMatchAppliesToCpeName compares exact criteria versions without lexical case', () => { + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria: 'cpe:2.3:a:vendor:product:1.0.0-RC1:*:*:*:*:*:*:*' + }, + 'cpe:2.3:a:vendor:product:1.0.0-rc1:*:*:*:*:*:*:*' + ) + ).toBe(true); + }); + + test('cpeMatchAppliesToCpeName rejects an exact version criterion with a different platform component', () => { + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:6.1.1:*:android:*:*:*:*:*', + vulnerable: true + }, + 'cpe:2.3:a:palo_alto_networks:globalprotect_app:6.1.1:*:macos:*:*:*:*:*', + undefined, + { requireVulnerable: true } + ) + ).toBe(false); + }); + + test('cpeMatchAppliesToCpeName keeps wildcard optional input components broad', () => { + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria: 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:android:*:*:*:*:*', + vulnerable: true, + versionStartIncluding: '6.1', + versionEndExcluding: '6.1.13' + }, + 'cpe:2.3:a:palo_alto_networks:globalprotect_app:6.1.1:*:*:*:*:*:*:*', + undefined, + { requireVulnerable: true } + ) + ).toBe(true); + }); + + test('cpeMatchAppliesToCpeName treats the CPE NA version as nonnumeric for range boundaries', () => { + const cpeName = 'cpe:2.3:a:microsoft:edge:-:*:*:*:*:android:*:*'; + const criteria = 'cpe:2.3:a:microsoft:edge:*:*:*:*:*:android:*:*'; + + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria, + vulnerable: true, + versionStartIncluding: '-' + }, + cpeName, + undefined, + { requireVulnerable: true } + ) + ).toBe(true); + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria, + vulnerable: true, + versionStartIncluding: '1.0.0', + versionEndExcluding: '128.0.2739.42' + }, + cpeName, + undefined, + { requireVulnerable: true } + ) + ).toBe(true); + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria, + vulnerable: true, + versionStartIncluding: '1.0.0', + versionEndExcluding: '148.0.3967.55' + }, + cpeName, + undefined, + { requireVulnerable: true } + ) + ).toBe(true); + }); + + test('cpeMatchAppliesToCpeName still honors exact and exclusive CPE NA criteria', () => { + const cpeName = 'cpe:2.3:a:microsoft:edge:-:*:*:*:*:android:*:*'; + + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria: 'cpe:2.3:a:microsoft:edge:1.0.0:*:*:*:*:android:*:*' + }, + cpeName + ) + ).toBe(false); + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria: 'cpe:2.3:a:microsoft:edge:*:*:*:*:*:android:*:*', + versionStartExcluding: '-' + }, + cpeName + ) + ).toBe(false); + expect( + cpe.cpeMatchAppliesToCpeName( + { + criteria: 'cpe:2.3:a:microsoft:edge:*:*:*:*:*:android:*:*', + versionEndExcluding: '-' + }, + cpeName + ) + ).toBe(false); + }); + + test('cpeMatchAppliesToCpeName requires vulnerability on the matching entry when requested', () => { + const cpeName = 'cpe:2.3:a:vendor:product:1.0:*:*:*:*:*:*:*'; + const matchingCriteria = 'cpe:2.3:a:vendor:product:1.0:*:*:*:*:*:*:*'; + + expect( + cpe.cpeMatchAppliesToCpeName({ criteria: matchingCriteria, vulnerable: true }, cpeName, undefined, { + requireVulnerable: true + }) + ).toBe(true); + expect( + cpe.cpeMatchAppliesToCpeName({ criteria: matchingCriteria, vulnerable: false }, cpeName, undefined, { + requireVulnerable: true + }) + ).toBe(false); + expect( + cpe.cpeMatchAppliesToCpeName({ criteria: matchingCriteria }, cpeName, undefined, { + requireVulnerable: true + }) + ).toBe(false); + expect(cpe.cpeMatchAppliesToCpeName({ criteria: matchingCriteria, vulnerable: false }, cpeName)).toBe(true); + }); + test('cpeMatchOverlapsVirtualMatchStringRange matches exact criteria inside the requested range', () => { const virtualMatchStringRange = { matchString: cpe.parseVirtualMatchString('cpe:2.3:o:linux:linux_kernel'), @@ -247,6 +508,40 @@ describe('CPE helpers', () => { ).toBe(false); }); + test('cpeMatchOverlapsVirtualMatchStringRange applies qualifier precedence to range overlap', () => { + const matchString = cpe.parseVirtualMatchString('cpe:2.3:a:vendor:product'); + + expect( + cpe.cpeMatchOverlapsVirtualMatchStringRange( + { + criteria: 'cpe:2.3:a:vendor:product:*:*:*:*:*:*:*:*', + versionEndExcluding: '1.0.0' + }, + { + matchString, + versionStart: '1.0.0-rc1', + versionStartType: 'including', + versionEnd: '1.0.0', + versionEndType: 'including' + } + ) + ).toBe(true); + + expect( + cpe.cpeMatchOverlapsVirtualMatchStringRange( + { + criteria: 'cpe:2.3:a:vendor:product:*:*:*:*:*:*:*:*', + versionEndExcluding: '1.0.0-rc1' + }, + { + matchString, + versionStart: '1.0.0', + versionStartType: 'including' + } + ) + ).toBe(false); + }); + test('cpeMatchOverlapsVirtualMatchStringRange matches upper-bounded wildcard criteria before the excluded boundary', () => { const cpeMatch = { criteria: 'cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*', @@ -281,7 +576,7 @@ describe('CPE helpers', () => { ).toBe(false); }); - test('cpeMatchMatchesVirtualMatchString matches full and partial NVD-style match strings', () => { + test('cpeMatchMatchesVirtualMatchString matches full and partial match strings', () => { const windowsX86 = { criteria: 'cpe:2.3:o:microsoft:windows_10_1809:-:*:*:*:*:*:x86:*' }; @@ -293,6 +588,30 @@ describe('CPE helpers', () => { expect(cpe.cpeMatchMatchesVirtualMatchString(windowsX86, 'cpe:2.3:o:microsoft:windows_11_23h2')).toBe(false); }); + test('virtualMatchString comparisons are insensitive to lexical case', () => { + const mixedCaseCriteria = { + criteria: 'cpe:2.3:a:Palo_Alto_Networks:GlobalProtect_App:*:*:*:*:*:macOS:*:*', + versionStartIncluding: '6.0', + versionEndExcluding: '6.1' + }; + + expect( + cpe.cpeMatchMatchesVirtualMatchString( + mixedCaseCriteria, + 'cpe:2.3:a:palo_alto_networks:globalprotect_app:*:*:*:*:*:macos:*:*' + ) + ).toBe(true); + expect( + cpe.cpeMatchOverlapsVirtualMatchStringRange(mixedCaseCriteria, { + matchString: cpe.parseVirtualMatchString('cpe:2.3:a:palo_alto_networks:globalprotect_app'), + versionStart: '6.0.1', + versionStartType: 'including', + versionEnd: '6.0.2', + versionEndType: 'excluding' + }) + ).toBe(true); + }); + test('hitAppliesToCpeName searches nested cpeApplicability nodes', () => { const hit = { _source: { diff --git a/utils/errors.js b/utils/errors.js index ca612c0..4c126e0 100644 --- a/utils/errors.js +++ b/utils/errors.js @@ -70,6 +70,13 @@ class errors { return makeError('VERSION_RANGE_TYPE', `The ${param} parameter must be including or excluding.`); } + versionRangeOrderError() { + return makeError( + 'VERSION_RANGE_ORDER', + 'The versionStart parameter must be less than versionEnd. Equal values require both bounds to be including.' + ); + } + virtualMatchStringVersionRangeFormatError() { return makeError( 'VIRTUAL_MATCH_STRING_RANGE_FORMAT', @@ -121,6 +128,17 @@ class errors { return makeError('SEARCH_BODY_OBJECT', 'The /search request body must be a JSON object.'); } + searchContentTypeError() { + return makeError( + 'SEARCH_CONTENT_TYPE', + 'The /search request body must use the application/json media type with UTF-8 encoding.' + ); + } + + malformedJsonError() { + return makeError('MALFORMED_JSON', 'The request body contains malformed JSON.'); + } + searchQueryUnsupportedError() { return makeError( 'SEARCH_QUERY_UNSUPPORTED', @@ -132,6 +150,21 @@ class errors { return makeError('SEARCH_FIELD_STRING', `The ${field} field must be a non-empty string.`); } + searchFieldLengthError(field, maxLength) { + return makeError('SEARCH_FIELD_LENGTH', `The ${field} field must be ${maxLength} characters or fewer.`); + } + + searchFieldTokenLimitError(field, maxTokens) { + return makeError('SEARCH_FIELD_TOKEN_LIMIT', `The ${field} field must contain ${maxTokens} or fewer terms.`); + } + + cveIdLengthError() { + return makeError( + 'CVE_ID_LENGTH', + `Each CVE ID in the cveIds field must be ${constants.MAX_CVE_ID_LENGTH} characters or fewer.` + ); + } + cvssV2SeverityError(severity) { return makeError( 'INVALID_CVSSV2_SEVERITY', @@ -229,6 +262,10 @@ class errors { return makeError('WEB_SEARCH_UNSUPPORTED_FIELD', 'The /webSearch request body contains unsupported fields.'); } + webSearchDisabledError() { + return makeError('WEB_SEARCH_DISABLED', 'The /webSearch endpoint is disabled. Use POST /search instead.'); + } + openSearchError() { return makeError( 'OPEN_SEARCH_ERROR', @@ -236,6 +273,10 @@ class errors { ); } + searchNotReadyError() { + return makeError('SEARCH_NOT_READY', 'Search service is not ready. Please try again later.'); + } + upstreamSearchError() { return makeError('UPSTREAM_SEARCH_ERROR', 'Search backend returned an error. Please try again later.'); } @@ -243,6 +284,18 @@ class errors { searchQueueFullError() { return makeError('SEARCH_QUEUE_FULL', 'Search service is busy. Please retry later.'); } + + searchQueueTimeoutError() { + return makeError('SEARCH_QUEUE_TIMEOUT', 'Search service is busy. Please retry later.'); + } + + searchTimeoutError() { + return makeError('SEARCH_TIMEOUT', 'Search request timed out. Please try again later.'); + } + + searchRateLimitedError() { + return makeError('SEARCH_RATE_LIMITED', 'Too many search requests. Please retry later.'); + } } module.exports = { diff --git a/utils/errors.test.js b/utils/errors.test.js index f1a0206..682af30 100644 --- a/utils/errors.test.js +++ b/utils/errors.test.js @@ -99,6 +99,15 @@ describe('error response helpers', () => { message: 'The versionEndType parameter must be including or excluding.' } ], + [ + 'versionRangeOrderError', + [], + { + error: 'VERSION_RANGE_ORDER', + message: + 'The versionStart parameter must be less than versionEnd. Equal values require both bounds to be including.' + } + ], [ 'virtualMatchStringVersionRangeFormatError', [], @@ -174,6 +183,38 @@ describe('error response helpers', () => { message: 'Search service is busy. Please retry later.' } ], + [ + 'searchQueueTimeoutError', + [], + { + error: 'SEARCH_QUEUE_TIMEOUT', + message: 'Search service is busy. Please retry later.' + } + ], + [ + 'searchTimeoutError', + [], + { + error: 'SEARCH_TIMEOUT', + message: 'Search request timed out. Please try again later.' + } + ], + [ + 'searchRateLimitedError', + [], + { + error: 'SEARCH_RATE_LIMITED', + message: 'Too many search requests. Please retry later.' + } + ], + [ + 'searchNotReadyError', + [], + { + error: 'SEARCH_NOT_READY', + message: 'Search service is not ready. Please try again later.' + } + ], [ 'searchBodyError', [], @@ -182,6 +223,22 @@ describe('error response helpers', () => { message: 'The /search request body must be a JSON object.' } ], + [ + 'searchContentTypeError', + [], + { + error: 'SEARCH_CONTENT_TYPE', + message: 'The /search request body must use the application/json media type with UTF-8 encoding.' + } + ], + [ + 'malformedJsonError', + [], + { + error: 'MALFORMED_JSON', + message: 'The request body contains malformed JSON.' + } + ], [ 'searchQueryUnsupportedError', [], @@ -330,6 +387,14 @@ describe('error response helpers', () => { message: 'The /webSearch request body contains unsupported fields.' } ], + [ + 'webSearchDisabledError', + [], + { + error: 'WEB_SEARCH_DISABLED', + message: 'The /webSearch endpoint is disabled. Use POST /search instead.' + } + ], [ 'openSearchError', [], diff --git a/utils/featureFlags.js b/utils/featureFlags.js new file mode 100644 index 0000000..61ff67f --- /dev/null +++ b/utils/featureFlags.js @@ -0,0 +1,25 @@ +const { AppConfig } = require('cve-core'); + +const DEFAULT_WEB_SEARCH_ENABLED = true; + +function normalizeBoolean(value, defaultValue) { + if (value === undefined || value === null || value === '') return defaultValue; + if (typeof value === 'boolean') return value; + if (typeof value === 'number') return value !== 0; + + const normalized = String(value).trim().toLowerCase(); + if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) return true; + if (['false', '0', 'no', 'n', 'off'].includes(normalized)) return false; + + return defaultValue; +} + +function isWebSearchEnabled() { + if (!AppConfig.has('features.webSearchEnabled')) return DEFAULT_WEB_SEARCH_ENABLED; + + return normalizeBoolean(AppConfig.get('features.webSearchEnabled'), false); +} + +module.exports = { + isWebSearchEnabled +}; diff --git a/utils/featureFlags.test.js b/utils/featureFlags.test.js new file mode 100644 index 0000000..3d47d75 --- /dev/null +++ b/utils/featureFlags.test.js @@ -0,0 +1,44 @@ +const mockAppConfigHas = jest.fn(); +const mockAppConfigGet = jest.fn(); + +jest.mock('cve-core', () => ({ + AppConfig: { + get: mockAppConfigGet, + has: mockAppConfigHas + } +})); + +const featureFlags = require('./featureFlags'); + +describe('featureFlags', () => { + beforeEach(() => { + mockAppConfigHas.mockReset(); + mockAppConfigGet.mockReset(); + mockAppConfigHas.mockReturnValue(false); + }); + + test('keeps webSearch enabled when no override is configured', () => { + expect(featureFlags.isWebSearchEnabled()).toBe(true); + }); + + test.each([ + ['TRUE', true], + ['false', false], + ['1', true], + ['0', false], + [true, true], + [false, false] + ])('normalizes a configured webSearch value of %p', (configuredValue, expected) => { + mockAppConfigHas.mockReturnValue(true); + mockAppConfigGet.mockReturnValue(configuredValue); + + expect(featureFlags.isWebSearchEnabled()).toBe(expected); + }); + + test('fails closed when an explicitly configured webSearch value is invalid', () => { + mockAppConfigHas.mockReturnValue(true); + mockAppConfigGet.mockReturnValue('invalid'); + + expect(featureFlags.isWebSearchEnabled()).toBe(false); + }); +}); diff --git a/utils/requestLogging.js b/utils/requestLogging.js new file mode 100644 index 0000000..57e7248 --- /dev/null +++ b/utils/requestLogging.js @@ -0,0 +1,99 @@ +const { randomUUID } = require('crypto'); +const { AppConfig } = require('cve-core'); + +const DEFAULT_SUCCESS_SAMPLE_RATE = 0.1; +const DEFAULT_LOG_HEALTH_CHECKS = false; +const REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/; +const HEALTH_CHECK_PATHS = new Set(['/api/health-check', '/api/ready-check']); + +let requestLoggingConfig; + +function normalizeBoolean(value, defaultValue = false) { + if (value === undefined || value === null || value === '') return defaultValue; + if (typeof value === 'boolean') return value; + if (typeof value === 'number') return value !== 0; + + const normalized = String(value).trim().toLowerCase(); + if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) return true; + if (['false', '0', 'no', 'n', 'off'].includes(normalized)) return false; + + return defaultValue; +} + +function readSuccessSampleRate() { + if (!AppConfig.has('requestLogging.successSampleRate')) return DEFAULT_SUCCESS_SAMPLE_RATE; + + const value = Number(AppConfig.get('requestLogging.successSampleRate')); + if (Number.isFinite(value) && value >= 0 && value <= 1) return value; + + return DEFAULT_SUCCESS_SAMPLE_RATE; +} + +function readLogHealthChecks() { + if (!AppConfig.has('requestLogging.healthChecks')) return DEFAULT_LOG_HEALTH_CHECKS; + + return normalizeBoolean(AppConfig.get('requestLogging.healthChecks'), DEFAULT_LOG_HEALTH_CHECKS); +} + +function getRequestLoggingConfig() { + if (!requestLoggingConfig) { + requestLoggingConfig = Object.freeze({ + successSampleRate: readSuccessSampleRate(), + healthChecks: readLogHealthChecks() + }); + } + + return requestLoggingConfig; +} + +function getSuccessSampleRate() { + return getRequestLoggingConfig().successSampleRate; +} + +function shouldLogHealthChecks() { + return getRequestLoggingConfig().healthChecks; +} + +function getRedactedRequestPath(req) { + return req.path || String(req.originalUrl || req.url || '-').split('?')[0]; +} + +function resolveRequestId(req, generateId = randomUUID) { + const headerValue = req.get?.('x-request-id') ?? req.headers?.['x-request-id']; + const requestId = Array.isArray(headerValue) ? headerValue[0] : headerValue; + const normalized = typeof requestId === 'string' ? requestId.trim() : ''; + + return REQUEST_ID_PATTERN.test(normalized) ? normalized : generateId(); +} + +function assignRequestId(req, res, next) { + req.requestId = resolveRequestId(req); + res.setHeader('X-Request-ID', req.requestId); + next(); +} + +function shouldSkipAccessLog(req, res, random = Math.random) { + if (res.statusCode >= 400) return false; + + if (HEALTH_CHECK_PATHS.has(getRedactedRequestPath(req)) && !shouldLogHealthChecks()) { + return true; + } + + return random() >= getSuccessSampleRate(); +} + +function resetForTests() { + requestLoggingConfig = undefined; +} + +module.exports = { + assignRequestId, + getRedactedRequestPath, + getRequestLoggingConfig, + getSuccessSampleRate, + normalizeBoolean, + resetForTests, + resolveRequestId, + shouldLogHealthChecks, + shouldSkipAccessLog +}; diff --git a/utils/requestLogging.test.js b/utils/requestLogging.test.js new file mode 100644 index 0000000..1cae11e --- /dev/null +++ b/utils/requestLogging.test.js @@ -0,0 +1,156 @@ +const mockAppConfigHas = jest.fn(); +const mockAppConfigGet = jest.fn(); + +jest.mock('cve-core', () => ({ + AppConfig: { + get: mockAppConfigGet, + has: mockAppConfigHas + } +})); + +const requestLogging = require('./requestLogging'); + +describe('requestLogging', () => { + beforeEach(() => { + requestLogging.resetForTests(); + mockAppConfigHas.mockReset(); + mockAppConfigGet.mockReset(); + mockAppConfigHas.mockReturnValue(false); + }); + + test('resolveRequestId preserves safe caller IDs', () => { + expect( + requestLogging.resolveRequestId( + { + headers: { + 'x-request-id': 'caller-request_123' + } + }, + () => 'generated-id' + ) + ).toBe('caller-request_123'); + }); + + test.each(['contains spaces', 'contains\nnewline', '', 'a'.repeat(129)])( + 'resolveRequestId replaces invalid caller ID %p', + (requestId) => { + expect( + requestLogging.resolveRequestId( + { + headers: { + 'x-request-id': requestId + } + }, + () => 'generated-id' + ) + ).toBe('generated-id'); + } + ); + + test('assignRequestId attaches a generated ID to the request and response', () => { + const req = { + headers: {} + }; + const res = { + setHeader: jest.fn() + }; + const next = jest.fn(); + + requestLogging.assignRequestId(req, res, next); + + expect(req.requestId).toMatch(/^[0-9a-f-]{36}$/); + expect(res.setHeader).toHaveBeenCalledWith('X-Request-ID', req.requestId); + expect(next).toHaveBeenCalledTimes(1); + }); + + test.each(['/api/health-check', '/api/ready-check'])( + 'shouldSkipAccessLog suppresses successful health check %s by default', + (path) => { + expect( + requestLogging.shouldSkipAccessLog( + { + path + }, + { + statusCode: 200 + }, + () => 0 + ) + ).toBe(true); + } + ); + + test.each(['/api/health-check', '/api/ready-check'])( + 'shouldSkipAccessLog retains failed health check %s', + (path) => { + expect( + requestLogging.shouldSkipAccessLog( + { + path + }, + { + statusCode: 500 + }, + () => 1 + ) + ).toBe(false); + } + ); + + test('shouldSkipAccessLog samples successful requests at the configured rate', () => { + mockAppConfigHas.mockImplementation((path) => path === 'requestLogging.successSampleRate'); + mockAppConfigGet.mockReturnValue('0.25'); + const req = { + path: '/search' + }; + const res = { + statusCode: 200 + }; + + expect(requestLogging.shouldSkipAccessLog(req, res, () => 0.24)).toBe(false); + expect(requestLogging.shouldSkipAccessLog(req, res, () => 0.25)).toBe(true); + }); + + test('shouldSkipAccessLog retains all client and server errors', () => { + const req = { + path: '/search' + }; + + expect(requestLogging.shouldSkipAccessLog(req, { statusCode: 400 }, () => 1)).toBe(false); + expect(requestLogging.shouldSkipAccessLog(req, { statusCode: 500 }, () => 1)).toBe(false); + }); + + test('request logging settings fall back when config values are invalid', () => { + mockAppConfigHas.mockReturnValue(true); + mockAppConfigGet.mockReturnValue('invalid'); + + expect(requestLogging.getSuccessSampleRate()).toBe(0.1); + expect(requestLogging.shouldLogHealthChecks()).toBe(false); + }); + + test('successful health checks can be enabled through config', () => { + mockAppConfigHas.mockImplementation((path) => path === 'requestLogging.healthChecks'); + mockAppConfigGet.mockReturnValue('TRUE'); + + expect( + requestLogging.shouldSkipAccessLog( + { + path: '/api/health-check' + }, + { + statusCode: 200 + }, + () => 0 + ) + ).toBe(false); + }); + + test('request logging config is resolved once per process', () => { + const first = requestLogging.getRequestLoggingConfig(); + const second = requestLogging.getRequestLoggingConfig(); + + expect(first).toBe(second); + expect(Object.isFrozen(first)).toBe(true); + expect(mockAppConfigHas).toHaveBeenCalledTimes(2); + }); +}); diff --git a/utils/searchDeadline.js b/utils/searchDeadline.js new file mode 100644 index 0000000..ecb33c4 --- /dev/null +++ b/utils/searchDeadline.js @@ -0,0 +1,100 @@ +class SearchDeadlineExceededError extends Error { + constructor() { + super('Search request deadline exceeded.'); + this.name = 'SearchDeadlineExceededError'; + this.code = 'SEARCH_TIMEOUT'; + } +} + +class SearchClientDisconnectedError extends Error { + constructor() { + super('Search client disconnected.'); + this.name = 'SearchClientDisconnectedError'; + this.code = 'SEARCH_CLIENT_DISCONNECTED'; + } +} + +function isSearchTimeoutError(err) { + return err instanceof SearchDeadlineExceededError || err?.code === 'SEARCH_TIMEOUT' || err?.name === 'TimeoutError'; +} + +function isSearchClientDisconnectedError(err) { + return err instanceof SearchClientDisconnectedError || err?.code === 'SEARCH_CLIENT_DISCONNECTED'; +} + +function getAbortReason(signal) { + if (signal?.reason instanceof Error) return signal.reason; + + const err = new Error('Search request aborted.'); + err.name = 'AbortError'; + err.code = 'SEARCH_ABORTED'; + return err; +} + +function throwIfAborted(signal) { + if (signal?.aborted) throw getAbortReason(signal); +} + +function addOnceListener(emitter, eventName, listener) { + if (typeof emitter?.once !== 'function') return () => {}; + + emitter.once(eventName, listener); + return () => emitter.removeListener?.(eventName, listener); +} + +function createSearchRequestLifecycle(req, res, timeoutMs) { + const controller = new AbortController(); + let completed = false; + + const abort = (reason) => { + if (!completed && !controller.signal.aborted) { + controller.abort(reason); + } + }; + const removeRequestAbortedListener = addOnceListener(req, 'aborted', () => + abort(new SearchClientDisconnectedError()) + ); + const removeResponseCloseListener = addOnceListener(res, 'close', () => { + if (res?.writableEnded !== true) { + abort(new SearchClientDisconnectedError()); + } + }); + const timeoutId = setTimeout(() => abort(new SearchDeadlineExceededError()), timeoutMs); + timeoutId.unref?.(); + + function complete() { + if (completed) return; + + completed = true; + clearTimeout(timeoutId); + removeRequestAbortedListener(); + removeResponseCloseListener(); + } + + function throwIfRequestAborted() { + if ( + !controller.signal.aborted && + (req?.aborted === true || (res?.destroyed === true && res?.writableEnded !== true)) + ) { + abort(new SearchClientDisconnectedError()); + } + + throwIfAborted(controller.signal); + } + + return { + complete, + signal: controller.signal, + throwIfAborted: throwIfRequestAborted + }; +} + +module.exports = { + createSearchRequestLifecycle, + getAbortReason, + isSearchClientDisconnectedError, + isSearchTimeoutError, + SearchClientDisconnectedError, + SearchDeadlineExceededError, + throwIfAborted +}; diff --git a/utils/searchDeadline.test.js b/utils/searchDeadline.test.js new file mode 100644 index 0000000..cafbf3c --- /dev/null +++ b/utils/searchDeadline.test.js @@ -0,0 +1,128 @@ +const { EventEmitter } = require('events'); +const searchDeadline = require('./searchDeadline'); + +function captureTimers() { + const timers = []; + jest.spyOn(global, 'setTimeout').mockImplementation((callback, delay) => { + const timer = { + unref: jest.fn() + }; + timers.push({ + callback, + delay, + timer + }); + return timer; + }); + jest.spyOn(global, 'clearTimeout').mockImplementation(() => {}); + + return timers; +} + +function createLifecycle(timeoutMs = 30000) { + const req = new EventEmitter(); + const res = new EventEmitter(); + res.writableEnded = false; + + return { + lifecycle: searchDeadline.createSearchRequestLifecycle(req, res, timeoutMs), + req, + res + }; +} + +describe('searchDeadline', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('aborts the request lifecycle when the deadline expires', () => { + const timers = captureTimers(); + const { lifecycle } = createLifecycle(); + + timers.find(({ delay }) => delay === 30000).callback(); + + expect(lifecycle.signal.aborted).toBe(true); + expect(lifecycle.signal.reason).toMatchObject({ + name: 'SearchDeadlineExceededError', + code: 'SEARCH_TIMEOUT' + }); + }); + + test('complete clears lifecycle listeners and prevents a late deadline abort', () => { + const timers = captureTimers(); + const { lifecycle, req, res } = createLifecycle(); + + lifecycle.complete(); + timers.find(({ delay }) => delay === 30000).callback(); + + expect(lifecycle.signal.aborted).toBe(false); + expect(req.listenerCount('aborted')).toBe(0); + expect(res.listenerCount('close')).toBe(0); + expect(clearTimeout).toHaveBeenCalledTimes(1); + }); + + test('aborts when the request emits aborted', () => { + captureTimers(); + const { lifecycle, req } = createLifecycle(); + + req.emit('aborted'); + + expect(lifecycle.signal.reason).toMatchObject({ + name: 'SearchClientDisconnectedError', + code: 'SEARCH_CLIENT_DISCONNECTED' + }); + }); + + test('aborts when the response closes before it is written', () => { + captureTimers(); + const { lifecycle, res } = createLifecycle(); + + res.emit('close'); + + expect(lifecycle.signal.reason).toMatchObject({ + code: 'SEARCH_CLIENT_DISCONNECTED' + }); + }); + + test('does not abort when a completed response closes', () => { + captureTimers(); + const { lifecycle, res } = createLifecycle(); + res.writableEnded = true; + + res.emit('close'); + + expect(lifecycle.signal.aborted).toBe(false); + }); + + test('detects an already-destroyed response before writing', () => { + captureTimers(); + const { lifecycle, res } = createLifecycle(); + res.destroyed = true; + + expect(() => lifecycle.throwIfAborted()).toThrow(searchDeadline.SearchClientDisconnectedError); + }); + + test.each([ + [new searchDeadline.SearchDeadlineExceededError()], + [{ code: 'SEARCH_TIMEOUT' }], + [{ name: 'TimeoutError' }] + ])('recognizes search timeout error %#', (err) => { + expect(searchDeadline.isSearchTimeoutError(err)).toBe(true); + }); + + test.each([[new searchDeadline.SearchClientDisconnectedError()], [{ code: 'SEARCH_CLIENT_DISCONNECTED' }]])( + 'recognizes client disconnect error %#', + (err) => { + expect(searchDeadline.isSearchClientDisconnectedError(err)).toBe(true); + } + ); + + test('throwIfAborted throws the lifecycle abort reason', () => { + const controller = new AbortController(); + const reason = new searchDeadline.SearchDeadlineExceededError(); + controller.abort(reason); + + expect(() => searchDeadline.throwIfAborted(controller.signal)).toThrow(reason); + }); +}); diff --git a/utils/searchInFlightRegistry.js b/utils/searchInFlightRegistry.js new file mode 100644 index 0000000..07f4e85 --- /dev/null +++ b/utils/searchInFlightRegistry.js @@ -0,0 +1,118 @@ +const searchDeadline = require('./searchDeadline'); + +const entries = new Map(); + +class SearchOperationAbandonedError extends Error { + constructor() { + super('Shared search operation no longer has active waiters.'); + this.name = 'SearchOperationAbandonedError'; + this.code = 'SEARCH_OPERATION_ABANDONED'; + } +} + +function buildKey(type, value) { + return `${type}:${JSON.stringify(value)}`; +} + +function settleEntry(key, entry) { + entry.settled = true; + if (entries.get(key) === entry) { + entries.delete(key); + } +} + +function createEntry(key, createOperation) { + const controller = new AbortController(); + const entry = { + controller, + key, + promise: undefined, + settled: false, + waiterCount: 0 + }; + + entries.set(key, entry); + entry.promise = Promise.resolve().then(() => createOperation(controller.signal)); + entry.promise.then( + () => settleEntry(key, entry), + () => settleEntry(key, entry) + ); + + return entry; +} + +function releaseWaiter(entry) { + entry.waiterCount = Math.max(0, entry.waiterCount - 1); + if (!entry.settled && entry.waiterCount === 0 && !entry.controller.signal.aborted) { + settleEntry(entry.key, entry); + entry.controller.abort(new SearchOperationAbandonedError()); + } +} + +function waitForEntry(entry, signal) { + entry.waiterCount += 1; + + return new Promise((resolve, reject) => { + let settled = false; + + const finish = (callback, value) => { + if (settled) return; + + settled = true; + signal?.removeEventListener('abort', onAbort); + releaseWaiter(entry); + callback(value); + }; + const onAbort = () => finish(reject, searchDeadline.getAbortReason(signal)); + + if (signal?.aborted) { + onAbort(); + return; + } + + signal?.addEventListener('abort', onAbort, { once: true }); + entry.promise.then( + (value) => finish(resolve, value), + (err) => finish(reject, err) + ); + }); +} + +function acquire(key, createOperation, { signal } = {}) { + searchDeadline.throwIfAborted(signal); + + let entry = entries.get(key); + const shared = Boolean(entry); + if (!entry) { + entry = createEntry(key, createOperation); + } + + return { + promise: waitForEntry(entry, signal), + shared + }; +} + +function clear() { + for (const entry of entries.values()) { + if (!entry.controller.signal.aborted) { + entry.controller.abort(new SearchOperationAbandonedError()); + } + } + entries.clear(); +} + +function getState() { + return { + operationCount: entries.size, + waiterCount: [...entries.values()].reduce((total, entry) => total + entry.waiterCount, 0) + }; +} + +module.exports = { + acquire, + buildKey, + clear, + getState, + SearchOperationAbandonedError +}; diff --git a/utils/searchInFlightRegistry.test.js b/utils/searchInFlightRegistry.test.js new file mode 100644 index 0000000..aed7ad9 --- /dev/null +++ b/utils/searchInFlightRegistry.test.js @@ -0,0 +1,133 @@ +const searchDeadline = require('./searchDeadline'); +const searchInFlightRegistry = require('./searchInFlightRegistry'); + +function deferred() { + const state = {}; + state.promise = new Promise((resolve, reject) => { + state.resolve = resolve; + state.reject = reject; + }); + return state; +} + +describe('searchInFlightRegistry', () => { + beforeEach(() => { + searchInFlightRegistry.clear(); + }); + + afterEach(() => { + searchInFlightRegistry.clear(); + }); + + test('coalesces waiters for the same operation key', async () => { + const operation = deferred(); + const createOperation = jest.fn(() => operation.promise); + const first = searchInFlightRegistry.acquire('same-key', createOperation); + const second = searchInFlightRegistry.acquire('same-key', createOperation); + await Promise.resolve(); + + expect(first.shared).toBe(false); + expect(second.shared).toBe(true); + expect(createOperation).toHaveBeenCalledTimes(1); + expect(searchInFlightRegistry.getState()).toStrictEqual({ + operationCount: 1, + waiterCount: 2 + }); + + operation.resolve('result'); + + await expect(first.promise).resolves.toBe('result'); + await expect(second.promise).resolves.toBe('result'); + expect(searchInFlightRegistry.getState()).toStrictEqual({ + operationCount: 0, + waiterCount: 0 + }); + }); + + test('keeps shared work active while another waiter remains', async () => { + const operation = deferred(); + const firstController = new AbortController(); + const secondController = new AbortController(); + let operationSignal; + const createOperation = jest.fn((signal) => { + operationSignal = signal; + return operation.promise; + }); + const first = searchInFlightRegistry.acquire('same-key', createOperation, { + signal: firstController.signal + }); + const second = searchInFlightRegistry.acquire('same-key', createOperation, { + signal: secondController.signal + }); + await Promise.resolve(); + + firstController.abort(new searchDeadline.SearchClientDisconnectedError()); + + await expect(first.promise).rejects.toMatchObject({ + code: 'SEARCH_CLIENT_DISCONNECTED' + }); + expect(operationSignal.aborted).toBe(false); + expect(searchInFlightRegistry.getState().waiterCount).toBe(1); + + operation.resolve('result'); + await expect(second.promise).resolves.toBe('result'); + }); + + test('aborts shared work after its final waiter cancels', async () => { + const operation = deferred(); + const controller = new AbortController(); + let operationSignal; + const acquired = searchInFlightRegistry.acquire( + 'same-key', + (signal) => { + operationSignal = signal; + return operation.promise; + }, + { + signal: controller.signal + } + ); + await Promise.resolve(); + + controller.abort(new searchDeadline.SearchDeadlineExceededError()); + + await expect(acquired.promise).rejects.toMatchObject({ + code: 'SEARCH_TIMEOUT' + }); + expect(operationSignal.reason).toMatchObject({ + code: 'SEARCH_OPERATION_ABANDONED' + }); + expect(searchInFlightRegistry.getState().operationCount).toBe(0); + + const replacementOperation = jest.fn(() => Promise.resolve('replacement')); + const replacement = searchInFlightRegistry.acquire('same-key', replacementOperation); + + expect(replacement.shared).toBe(false); + await expect(replacement.promise).resolves.toBe('replacement'); + expect(replacementOperation).toHaveBeenCalledTimes(1); + + operation.reject(operationSignal.reason); + await expect(operation.promise).rejects.toMatchObject({ + code: 'SEARCH_OPERATION_ABANDONED' + }); + }); + + test('does not start work for an already-aborted waiter', () => { + const controller = new AbortController(); + const createOperation = jest.fn(); + controller.abort(new searchDeadline.SearchDeadlineExceededError()); + + expect(() => + searchInFlightRegistry.acquire('same-key', createOperation, { + signal: controller.signal + }) + ).toThrow(searchDeadline.SearchDeadlineExceededError); + expect(createOperation).not.toHaveBeenCalled(); + }); + + test('buildKey preserves operation type boundaries', () => { + expect(searchInFlightRegistry.buildKey('candidate', ['CVE-2024-0001'])).not.toBe( + searchInFlightRegistry.buildKey('pageFetch', ['CVE-2024-0001']) + ); + }); +}); diff --git a/utils/searchPerformance.js b/utils/searchPerformance.js index b5ed5d4..1240cfc 100644 --- a/utils/searchPerformance.js +++ b/utils/searchPerformance.js @@ -12,22 +12,46 @@ function roundMs(value) { return value === undefined ? undefined : Math.round(value * 100) / 100; } +function getCacheStatus(metrics, postFilters) { + if (!Object.keys(postFilters ?? {}).length) return 'not_applicable'; + if (metrics.cacheHit === true) return 'hit'; + if (metrics.inFlightHit === true) return 'in_flight'; + + return 'miss'; +} + function logSearchPerformance(metrics, searchOptions, responseOptions, postFilters) { - if (!searchProvider.shouldLogSearchPerformance()) return; + const outcome = metrics.outcome ?? 'success'; + const includeDetailedTimings = searchProvider.shouldLogSearchPerformance(); + if (outcome === 'success' && !includeDetailedTimings) return; - console.log( - JSON.stringify({ - event: 'search.performance', - route: '/search', - strategy: metrics.strategy, + const event = { + event: 'search.performance', + route: '/search', + requestId: metrics.requestId, + outcome, + statusCode: metrics.statusCode, + timeoutSource: metrics.timeoutSource, + abortReason: metrics.abortReason, + strategy: metrics.strategy, + cacheStatus: getCacheStatus(metrics, postFilters), + coldSearchQueueWaitElapsedMs: roundMs(metrics.coldSearchQueueWaitElapsedMs), + coldSearchQueueState: metrics.coldSearchQueueState, + totalElapsedMs: roundMs(metrics.totalElapsedMs) + }; + + if (includeDetailedTimings) { + Object.assign(event, { optimizedFallback: metrics.optimizedFallback === true, cacheHit: metrics.cacheHit === true, inFlightHit: metrics.inFlightHit === true, pageFetchInFlightHit: metrics.pageFetchInFlightHit === true, + openSearchOperations: metrics.openSearchOperations ?? [], + maxOpenSearchActiveRequests: metrics.maxOpenSearchActiveRequests, postFilters: Object.keys(postFilters ?? {}), - requestedFrom: responseOptions?.from ?? searchOptions.from, - requestedSize: responseOptions?.resultsPerPage ?? searchOptions.resultsPerPage, - openSearchSize: searchOptions.resultsPerPage, + requestedFrom: responseOptions?.from ?? searchOptions?.from, + requestedSize: responseOptions?.resultsPerPage ?? searchOptions?.resultsPerPage, + openSearchSize: searchOptions?.resultsPerPage, candidateCount: metrics.candidateCount, filteredCount: metrics.filteredCount, returnedCount: metrics.returnedCount, @@ -35,18 +59,18 @@ function logSearchPerformance(metrics, searchOptions, responseOptions, postFilte pageFetchTookMs: metrics.pageFetchTookMs, openSearchElapsedMs: roundMs(metrics.openSearchElapsedMs), postFilterElapsedMs: roundMs(metrics.postFilterElapsedMs), - coldSearchQueueWaitElapsedMs: roundMs(metrics.coldSearchQueueWaitElapsedMs), - coldSearchQueueState: metrics.coldSearchQueueState, inFlightWaitElapsedMs: roundMs(metrics.inFlightWaitElapsedMs), pageFetchElapsedMs: roundMs(metrics.pageFetchElapsedMs), - pageFetchWaitElapsedMs: roundMs(metrics.pageFetchWaitElapsedMs), - totalElapsedMs: roundMs(metrics.totalElapsedMs) - }) - ); + pageFetchWaitElapsedMs: roundMs(metrics.pageFetchWaitElapsedMs) + }); + } + + console.log(JSON.stringify(event)); } module.exports = { elapsedMs, + getCacheStatus, logSearchPerformance, markTime, roundMs diff --git a/utils/searchPerformance.test.js b/utils/searchPerformance.test.js index 6930be5..5ec42f1 100644 --- a/utils/searchPerformance.test.js +++ b/utils/searchPerformance.test.js @@ -21,10 +21,15 @@ describe('searchPerformance', () => { searchPerformance.logSearchPerformance( { + requestId: 'request-123', + outcome: 'success', + statusCode: 200, strategy: 'cpe-candidate', cacheHit: true, inFlightHit: true, pageFetchInFlightHit: true, + openSearchOperations: ['cpe-candidate', 'page-fetch'], + maxOpenSearchActiveRequests: 3, candidateCount: 10000, filteredCount: 4, returnedCount: 1, @@ -60,11 +65,17 @@ describe('searchPerformance', () => { expect(JSON.parse(console.log.mock.calls[0][0])).toStrictEqual({ event: 'search.performance', route: '/search', + requestId: 'request-123', + outcome: 'success', + statusCode: 200, strategy: 'cpe-candidate', + cacheStatus: 'hit', optimizedFallback: false, cacheHit: true, inFlightHit: true, pageFetchInFlightHit: true, + openSearchOperations: ['cpe-candidate', 'page-fetch'], + maxOpenSearchActiveRequests: 3, postFilters: ['virtualMatchStringRange'], requestedFrom: 0, requestedSize: 1, @@ -92,8 +103,63 @@ describe('searchPerformance', () => { test('logSearchPerformance does not emit logs when disabled', () => { mockShouldLogSearchPerformance.mockReturnValue(false); - searchPerformance.logSearchPerformance({}, { resultsPerPage: 1, from: 0 }, undefined, {}); + searchPerformance.logSearchPerformance( + { + outcome: 'success' + }, + { resultsPerPage: 1, from: 0 }, + undefined, + {} + ); expect(console.log).not.toHaveBeenCalled(); }); + + test('logSearchPerformance always emits sanitized failure outcomes', () => { + mockShouldLogSearchPerformance.mockReturnValue(false); + + searchPerformance.logSearchPerformance( + { + requestId: 'request-456', + outcome: 'queue_timeout', + statusCode: 429, + strategy: 'cpe-candidate', + coldSearchQueueWaitElapsedMs: 10000.123, + coldSearchQueueState: { + activeCount: 10, + queuedCount: 499, + maxConcurrent: 10, + maxQueueDepth: 500, + maxWaitMs: 10000 + }, + totalElapsedMs: 10001.456 + }, + undefined, + undefined, + { + virtualMatchString: 'sensitive-value' + } + ); + + expect(console.log).toHaveBeenCalledTimes(1); + expect(JSON.parse(console.log.mock.calls[0][0])).toStrictEqual({ + event: 'search.performance', + route: '/search', + requestId: 'request-456', + outcome: 'queue_timeout', + statusCode: 429, + strategy: 'cpe-candidate', + cacheStatus: 'miss', + coldSearchQueueWaitElapsedMs: 10000.12, + coldSearchQueueState: { + activeCount: 10, + queuedCount: 499, + maxConcurrent: 10, + maxQueueDepth: 500, + maxWaitMs: 10000 + }, + totalElapsedMs: 10001.46 + }); + expect(console.log.mock.calls[0][0]).not.toContain('sensitive-value'); + }); }); diff --git a/utils/searchPostFilterCache.js b/utils/searchPostFilterCache.js index a573859..f7ed9d8 100644 --- a/utils/searchPostFilterCache.js +++ b/utils/searchPostFilterCache.js @@ -1,7 +1,6 @@ const searchProvider = require('./searchProvider'); const entries = new Map(); -const inFlightEntries = new Map(); function normalizeForKey(value) { if (Array.isArray(value)) return value.map(normalizeForKey); @@ -29,15 +28,6 @@ function buildCacheKey(candidateBody, postFilters) { ); } -function buildInFlightKey(type, value) { - return JSON.stringify( - normalizeForKey({ - type, - value - }) - ); -} - function get(cacheKey, now = Date.now()) { const entry = entries.get(cacheKey); if (!entry) return null; @@ -70,36 +60,14 @@ function set(cacheKey, value, now = Date.now()) { } } -function getInFlight(cacheKey) { - return inFlightEntries.get(cacheKey); -} - -function setInFlight(cacheKey, promise) { - inFlightEntries.set(cacheKey, promise); - - const clear = () => { - if (inFlightEntries.get(cacheKey) === promise) { - inFlightEntries.delete(cacheKey); - } - }; - - promise.then(clear, clear); - - return promise; -} - function clear() { entries.clear(); - inFlightEntries.clear(); } module.exports = { buildCacheKey, - buildInFlightKey, clear, get, - getInFlight, normalizeForKey, - set, - setInFlight + set }; diff --git a/utils/searchPostFilterCache.test.js b/utils/searchPostFilterCache.test.js index 50a4737..77aff79 100644 --- a/utils/searchPostFilterCache.test.js +++ b/utils/searchPostFilterCache.test.js @@ -1,14 +1,5 @@ const searchPostFilterCache = require('./searchPostFilterCache'); -function deferred() { - const state = {}; - state.promise = new Promise((resolve, reject) => { - state.resolve = resolve; - state.reject = reject; - }); - return state; -} - describe('searchPostFilterCache', () => { beforeEach(() => { searchPostFilterCache.clear(); @@ -55,12 +46,6 @@ describe('searchPostFilterCache', () => { expect(left).toBe(right); }); - test('buildInFlightKey is stable for equivalent structured values', () => { - expect(searchPostFilterCache.buildInFlightKey('pageFetch', ['CVE-2024-0001'])).toBe( - searchPostFilterCache.buildInFlightKey('pageFetch', ['CVE-2024-0001']) - ); - }); - test('get returns cached values until the entry expires', () => { const key = 'cache-key'; const value = { @@ -73,33 +58,4 @@ describe('searchPostFilterCache', () => { expect(searchPostFilterCache.get(key, 1001)).toBe(value); expect(searchPostFilterCache.get(key, 61001)).toBeNull(); }); - - test('setInFlight returns an existing in-flight promise until it resolves', async () => { - const pending = deferred(); - - searchPostFilterCache.setInFlight('cache-key', pending.promise); - - expect(searchPostFilterCache.getInFlight('cache-key')).toBe(pending.promise); - - pending.resolve({ - candidateCount: 1, - filteredCveIds: ['CVE-2024-0001'] - }); - await pending.promise; - await Promise.resolve(); - - expect(searchPostFilterCache.getInFlight('cache-key')).toBeUndefined(); - }); - - test('setInFlight clears rejected in-flight promises', async () => { - const pending = deferred(); - - searchPostFilterCache.setInFlight('cache-key', pending.promise); - - pending.reject(new Error('candidate search failed')); - await expect(pending.promise).rejects.toThrow('candidate search failed'); - await Promise.resolve(); - - expect(searchPostFilterCache.getInFlight('cache-key')).toBeUndefined(); - }); }); diff --git a/utils/searchPostFilterWorkQueue.js b/utils/searchPostFilterWorkQueue.js index 704efcf..8180f97 100644 --- a/utils/searchPostFilterWorkQueue.js +++ b/utils/searchPostFilterWorkQueue.js @@ -1,4 +1,5 @@ const searchProvider = require('./searchProvider'); +const searchDeadline = require('./searchDeadline'); let activeCount = 0; const queue = []; @@ -12,6 +13,16 @@ class SearchPostFilterQueueFullError extends Error { } } +class SearchPostFilterQueueTimeoutError extends Error { + constructor(state, waitElapsedMs) { + super('CPE post-filter cold search queue wait timed out.'); + this.name = 'SearchPostFilterQueueTimeoutError'; + this.code = 'SEARCH_QUEUE_TIMEOUT'; + this.state = state; + this.waitElapsedMs = waitElapsedMs; + } +} + function getMaxConcurrent() { return searchProvider.getPostFilterColdSearchConcurrency(); } @@ -20,12 +31,17 @@ function getMaxQueueDepth() { return searchProvider.getPostFilterColdSearchMaxQueueDepth(); } +function getMaxWaitMs() { + return searchProvider.getPostFilterColdSearchMaxWaitMs(); +} + function getState() { return { activeCount, queuedCount: queue.length, maxConcurrent: getMaxConcurrent(), - maxQueueDepth: getMaxQueueDepth() + maxQueueDepth: getMaxQueueDepth(), + maxWaitMs: getMaxWaitMs() }; } @@ -33,16 +49,23 @@ function isQueueFullError(err) { return err instanceof SearchPostFilterQueueFullError || err?.code === 'SEARCH_QUEUE_FULL'; } +function isQueueTimeoutError(err) { + return err instanceof SearchPostFilterQueueTimeoutError || err?.code === 'SEARCH_QUEUE_TIMEOUT'; +} + function drain() { const maxConcurrent = getMaxConcurrent(); while (activeCount < maxConcurrent && queue.length) { const item = queue.shift(); + clearTimeout(item.timeoutId); + item.signal?.removeEventListener('abort', item.onAbort); activeCount += 1; void (async () => { try { - item.resolve(await item.task()); + searchDeadline.throwIfAborted(item.signal); + item.resolve(await item.task(item.signal)); } catch (err) { item.reject(err); } finally { @@ -53,31 +76,74 @@ function drain() { } } -function run(task) { +function run(task, { signal } = {}) { return new Promise((resolve, reject) => { + try { + searchDeadline.throwIfAborted(signal); + } catch (err) { + reject(err); + return; + } + if (activeCount >= getMaxConcurrent() && queue.length >= getMaxQueueDepth()) { reject(new SearchPostFilterQueueFullError(getState())); return; } - queue.push({ + const item = { task, resolve, - reject - }); + reject, + queuedAt: process.hrtime.bigint(), + timeoutId: undefined, + signal, + onAbort: undefined + }; + + if (activeCount >= getMaxConcurrent()) { + item.onAbort = () => { + const queueIndex = queue.indexOf(item); + if (queueIndex === -1) return; + + queue.splice(queueIndex, 1); + clearTimeout(item.timeoutId); + reject(searchDeadline.getAbortReason(signal)); + drain(); + }; + signal?.addEventListener('abort', item.onAbort, { once: true }); + item.timeoutId = setTimeout(() => { + const queueIndex = queue.indexOf(item); + if (queueIndex === -1) return; + + queue.splice(queueIndex, 1); + signal?.removeEventListener('abort', item.onAbort); + const waitElapsedMs = Number(process.hrtime.bigint() - item.queuedAt) / 1e6; + reject(new SearchPostFilterQueueTimeoutError(getState(), waitElapsedMs)); + drain(); + }, getMaxWaitMs()); + item.timeoutId.unref?.(); + } + + queue.push(item); drain(); }); } function resetForTests() { activeCount = 0; + queue.forEach((item) => { + clearTimeout(item.timeoutId); + item.signal?.removeEventListener('abort', item.onAbort); + }); queue.length = 0; } module.exports = { SearchPostFilterQueueFullError, + SearchPostFilterQueueTimeoutError, getState, isQueueFullError, + isQueueTimeoutError, resetForTests, run }; diff --git a/utils/searchPostFilterWorkQueue.test.js b/utils/searchPostFilterWorkQueue.test.js index ea2ff89..02ef0b1 100644 --- a/utils/searchPostFilterWorkQueue.test.js +++ b/utils/searchPostFilterWorkQueue.test.js @@ -1,12 +1,15 @@ const mockGetPostFilterColdSearchConcurrency = jest.fn(); const mockGetPostFilterColdSearchMaxQueueDepth = jest.fn(); +const mockGetPostFilterColdSearchMaxWaitMs = jest.fn(); jest.mock('./searchProvider', () => ({ getPostFilterColdSearchConcurrency: mockGetPostFilterColdSearchConcurrency, - getPostFilterColdSearchMaxQueueDepth: mockGetPostFilterColdSearchMaxQueueDepth + getPostFilterColdSearchMaxQueueDepth: mockGetPostFilterColdSearchMaxQueueDepth, + getPostFilterColdSearchMaxWaitMs: mockGetPostFilterColdSearchMaxWaitMs })); const searchPostFilterWorkQueue = require('./searchPostFilterWorkQueue'); +const searchDeadline = require('./searchDeadline'); function deferred() { const state = {}; @@ -17,13 +20,38 @@ function deferred() { return state; } +function captureTimers() { + const timers = []; + jest.spyOn(global, 'setTimeout').mockImplementation((callback, delay) => { + const timer = { + unref: jest.fn() + }; + timers.push({ + callback, + delay, + timer + }); + return timer; + }); + jest.spyOn(global, 'clearTimeout').mockImplementation(() => {}); + + return timers; +} + describe('searchPostFilterWorkQueue', () => { beforeEach(() => { searchPostFilterWorkQueue.resetForTests(); mockGetPostFilterColdSearchConcurrency.mockReset(); mockGetPostFilterColdSearchMaxQueueDepth.mockReset(); + mockGetPostFilterColdSearchMaxWaitMs.mockReset(); mockGetPostFilterColdSearchConcurrency.mockReturnValue(1); mockGetPostFilterColdSearchMaxQueueDepth.mockReturnValue(1); + mockGetPostFilterColdSearchMaxWaitMs.mockReturnValue(10000); + }); + + afterEach(() => { + searchPostFilterWorkQueue.resetForTests(); + jest.restoreAllMocks(); }); test('runs no more than the configured number of cold searches concurrently', async () => { @@ -42,7 +70,8 @@ describe('searchPostFilterWorkQueue', () => { activeCount: 1, queuedCount: 1, maxConcurrent: 1, - maxQueueDepth: 1 + maxQueueDepth: 1, + maxWaitMs: 10000 }); first.resolve('first'); @@ -81,7 +110,8 @@ describe('searchPostFilterWorkQueue', () => { activeCount: 1, queuedCount: 1, maxConcurrent: 1, - maxQueueDepth: 1 + maxQueueDepth: 1, + maxWaitMs: 10000 } }); @@ -95,4 +125,76 @@ describe('searchPostFilterWorkQueue', () => { await expect(searchPostFilterWorkQueue.run(() => 'first')).resolves.toBe('first'); }); + + test('rejects queued work that exceeds the configured maximum wait', async () => { + const timers = captureTimers(); + const first = deferred(); + const secondTask = jest.fn(() => 'second'); + + const firstRun = searchPostFilterWorkQueue.run(() => first.promise); + const secondRun = searchPostFilterWorkQueue.run(secondTask); + const timeoutExpectation = expect(secondRun).rejects.toMatchObject({ + code: 'SEARCH_QUEUE_TIMEOUT', + waitElapsedMs: expect.any(Number), + state: { + activeCount: 1, + queuedCount: 0, + maxConcurrent: 1, + maxQueueDepth: 1, + maxWaitMs: 10000 + } + }); + await Promise.resolve(); + + timers.find(({ delay }) => delay === 10000).callback(); + await timeoutExpectation; + expect(secondTask).not.toHaveBeenCalled(); + + first.resolve('first'); + await expect(firstRun).resolves.toBe('first'); + }); + + test('removes queued work when its operation is aborted', async () => { + const first = deferred(); + const controller = new AbortController(); + const secondTask = jest.fn(); + + const firstRun = searchPostFilterWorkQueue.run(() => first.promise); + const secondRun = searchPostFilterWorkQueue.run(secondTask, { + signal: controller.signal + }); + await Promise.resolve(); + + controller.abort(new searchDeadline.SearchDeadlineExceededError()); + + await expect(secondRun).rejects.toMatchObject({ + code: 'SEARCH_TIMEOUT' + }); + expect(secondTask).not.toHaveBeenCalled(); + expect(searchPostFilterWorkQueue.getState().queuedCount).toBe(0); + + first.resolve('first'); + await expect(firstRun).resolves.toBe('first'); + }); + + test('passes the operation signal to active work', async () => { + const controller = new AbortController(); + const task = jest.fn( + (signal) => + new Promise((resolve, reject) => { + signal.addEventListener('abort', () => reject(signal.reason), { once: true }); + }) + ); + const run = searchPostFilterWorkQueue.run(task, { + signal: controller.signal + }); + await Promise.resolve(); + + controller.abort(new searchDeadline.SearchDeadlineExceededError()); + + await expect(run).rejects.toMatchObject({ + code: 'SEARCH_TIMEOUT' + }); + expect(task).toHaveBeenCalledWith(controller.signal); + }); }); diff --git a/utils/searchProvider.js b/utils/searchProvider.js index 7d3952b..929ec6b 100644 --- a/utils/searchProvider.js +++ b/utils/searchProvider.js @@ -1,10 +1,34 @@ -const { AdvancedSearchManager, AppConfig, BasicSearchManager, SearchProviderSpec, SearchReader } = require('cve-core'); +const { AppConfig, BasicSearchManager, SearchProviderSpec, SearchReader } = require('cve-core'); const constants = require('./constants'); +const searchDeadline = require('./searchDeadline'); + +class SearchProviderContractError extends Error { + constructor() { + super('Installed cve-core SearchReader does not expose the required OpenSearch client contract.'); + this.name = 'SearchProviderContractError'; + this.code = 'SEARCH_PROVIDER_CONTRACT'; + } +} let searchProviderConfig; -let advancedSearchManager; let basicSearchManager; let searchReader; +let activeOpenSearchRequestCount = 0; +const activeOpenSearchRequestsByOperation = new Map(); + +function configureClientRequestTimeout(client) { + if (client?.transport) { + client.transport.requestTimeout = getOpenSearchRequestTimeoutMs(); + client.transport.maxRetries = 0; + } + + return client; +} + +function configureManagerRequestTimeout(manager) { + configureClientRequestTimeout(manager?._searchReader?._client); + return manager; +} function normalizeBoolean(value, defaultValue = false) { if (value === undefined || value === null || value === '') return defaultValue; @@ -61,43 +85,177 @@ function getSearchProviderConfig() { return searchProviderConfig; } -function getAdvancedSearchManager() { - if (!advancedSearchManager) { - advancedSearchManager = new AdvancedSearchManager(getSearchProviderConfig()); +function getBasicSearchManager() { + if (!basicSearchManager) { + basicSearchManager = configureManagerRequestTimeout(new BasicSearchManager(getSearchProviderConfig())); } - return advancedSearchManager; + return basicSearchManager; } -function getBasicSearchManager() { - if (!basicSearchManager) { - basicSearchManager = new BasicSearchManager(getSearchProviderConfig()); +function validateSearchReader(reader) { + const hasIndex = typeof reader?._cveIndex === 'string' && reader._cveIndex.trim() !== ''; + const hasSearchClient = typeof reader?._client?.search === 'function'; + + if (!hasIndex || !hasSearchClient) { + throw new SearchProviderContractError(); } - return basicSearchManager; + return reader; } function getSearchReader() { if (!searchReader) { const config = getSearchProviderConfig(); - searchReader = new SearchReader(config.providerEndpoint, config.index, { - ignore_ssl_cert_check_errors: config.allowUnknownSslCerts - }); + const reader = validateSearchReader( + new SearchReader(config.providerEndpoint, config.index, { + ignore_ssl_cert_check_errors: config.allowUnknownSslCerts + }) + ); + configureClientRequestTimeout(reader._client); + searchReader = reader; } return searchReader; } -async function searchRaw(body) { - const reader = getSearchReader(); - const response = await reader._client.search({ - index: reader._cveIndex, - body +function getOpenSearchRequestState() { + return { + activeCount: activeOpenSearchRequestCount, + activeByOperation: Object.fromEntries(activeOpenSearchRequestsByOperation) + }; +} + +function beginOpenSearchRequest(operation) { + activeOpenSearchRequestCount += 1; + activeOpenSearchRequestsByOperation.set(operation, (activeOpenSearchRequestsByOperation.get(operation) ?? 0) + 1); + + return getOpenSearchRequestState(); +} + +function endOpenSearchRequest(operation) { + activeOpenSearchRequestCount = Math.max(0, activeOpenSearchRequestCount - 1); + + const operationCount = activeOpenSearchRequestsByOperation.get(operation) ?? 0; + if (operationCount <= 1) { + activeOpenSearchRequestsByOperation.delete(operation); + } else { + activeOpenSearchRequestsByOperation.set(operation, operationCount - 1); + } +} + +function awaitAbortableRequest(request, signal) { + if (!signal) return Promise.resolve(request); + + searchDeadline.throwIfAborted(signal); + + return new Promise((resolve, reject) => { + let settled = false; + + const finish = (callback, value) => { + if (settled) return; + + settled = true; + signal.removeEventListener('abort', onAbort); + callback(value); + }; + const onAbort = () => { + try { + request?.abort?.(); + } catch (_err) { + // The request still rejects with the lifecycle's sanitized abort reason. + } + finish(reject, searchDeadline.getAbortReason(signal)); + }; + + signal.addEventListener('abort', onAbort, { once: true }); + Promise.resolve(request).then( + (response) => finish(resolve, response), + (err) => finish(reject, signal.aborted ? searchDeadline.getAbortReason(signal) : err) + ); }); +} + +async function executeOpenSearchRequest(createRequest, { signal, operation, onStart } = {}) { + searchDeadline.throwIfAborted(signal); + const state = beginOpenSearchRequest(operation); + + try { + onStart?.(state); + const response = await awaitAbortableRequest(createRequest(), signal); + + return response; + } finally { + endOpenSearchRequest(operation); + } +} + +async function searchOpenSearch(body, { signal, operation = 'search', onStart } = {}) { + searchDeadline.throwIfAborted(signal); + const reader = getSearchReader(); + const response = await executeOpenSearchRequest( + () => + reader._client.search( + { + index: reader._cveIndex, + body + }, + { + maxRetries: 0, + requestTimeout: getOpenSearchRequestTimeoutMs() + } + ), + { + signal, + operation, + onStart + } + ); return response.body; } +async function checkOpenSearchReadiness({ signal } = {}) { + searchDeadline.throwIfAborted(signal); + const reader = getSearchReader(); + await executeOpenSearchRequest( + () => + reader._client.search( + { + index: reader._cveIndex, + body: { + query: { + match_all: {} + }, + size: 0, + terminate_after: 1, + track_total_hits: false + } + }, + { + maxRetries: 0, + requestTimeout: getOpenSearchReadinessTimeoutMs() + } + ), + { + signal, + operation: 'readiness' + } + ); + + return true; +} + +function isSearchProviderContractError(err) { + return err instanceof SearchProviderContractError || err?.code === 'SEARCH_PROVIDER_CONTRACT'; +} + +function getOpenSearchReadinessTimeoutMs() { + return getIntegerConfig('search.readinessTimeoutMs', constants.SEARCH_READINESS_TIMEOUT_MS, { + min: 1 + }); +} + function shouldLogSearchPerformance() { if (!AppConfig.has('search.logPerformanceTimings')) return false; @@ -128,26 +286,53 @@ function getPostFilterColdSearchMaxQueueDepth() { ); } +function getPostFilterColdSearchMaxWaitMs() { + return getIntegerConfig( + 'search.postFilterColdSearchMaxWaitMs', + constants.SEARCH_POST_FILTER_COLD_SEARCH_MAX_WAIT_MS, + { min: 1 } + ); +} + +function getOpenSearchRequestTimeoutMs() { + return getIntegerConfig('search.openSearchRequestTimeoutMs', constants.SEARCH_OPEN_SEARCH_REQUEST_TIMEOUT_MS, { + min: 1 + }); +} + +function getSearchRequestDeadlineMs() { + return getIntegerConfig('search.requestDeadlineMs', constants.SEARCH_REQUEST_DEADLINE_MS, { min: 1 }); +} + function resetSearchProviderForTests() { searchProviderConfig = undefined; - advancedSearchManager = undefined; basicSearchManager = undefined; searchReader = undefined; + activeOpenSearchRequestCount = 0; + activeOpenSearchRequestsByOperation.clear(); } module.exports = { - getAdvancedSearchManager, + checkOpenSearchReadiness, getBasicSearchManager, getAllowUnknownSslCerts, getIntegerConfig, + getOpenSearchRequestState, + getOpenSearchReadinessTimeoutMs, getPostFilterCacheMaxEntries, getPostFilterCacheTtlMs, getPostFilterColdSearchConcurrency, getPostFilterColdSearchMaxQueueDepth, + getPostFilterColdSearchMaxWaitMs, + getOpenSearchRequestTimeoutMs, + getSearchRequestDeadlineMs, getSearchProviderConfig, getSearchReader, + isSearchProviderContractError, normalizeBoolean, resetSearchProviderForTests, - searchRaw, - shouldLogSearchPerformance + searchOpenSearch, + shouldLogSearchPerformance, + validateSearchReader, + SearchProviderContractError }; diff --git a/utils/searchProvider.test.js b/utils/searchProvider.test.js index e1fcb82..c817ad9 100644 --- a/utils/searchProvider.test.js +++ b/utils/searchProvider.test.js @@ -1,8 +1,13 @@ -const mockAdvancedSearchManager = jest.fn(function AdvancedSearchManager(spec) { - this.spec = spec; -}); const mockBasicSearchManager = jest.fn(function BasicSearchManager(spec) { this.spec = spec; + this._searchReader = { + _client: { + transport: { + maxRetries: 3, + requestTimeout: 30000 + } + } + }; }); const mockClientSearch = jest.fn(); const mockSearchReader = jest.fn(function SearchReader(endpoint, index, options) { @@ -10,7 +15,11 @@ const mockSearchReader = jest.fn(function SearchReader(endpoint, index, options) this._cveIndex = index; this._options = options; this._client = { - search: mockClientSearch + search: mockClientSearch, + transport: { + maxRetries: 3, + requestTimeout: 30000 + } }; }); const mockGetDefaultSearchProviderSpec = jest.fn(); @@ -21,7 +30,6 @@ const mockSearchProviderSpec = { }; jest.mock('cve-core', () => ({ - AdvancedSearchManager: mockAdvancedSearchManager, AppConfig: { get: mockAppConfigGet, has: mockAppConfigHas @@ -32,11 +40,20 @@ jest.mock('cve-core', () => ({ })); const searchProvider = require('./searchProvider'); +const searchDeadline = require('./searchDeadline'); + +function deferred() { + const state = {}; + state.promise = new Promise((resolve, reject) => { + state.resolve = resolve; + state.reject = reject; + }); + return state; +} describe('searchProvider', () => { beforeEach(() => { searchProvider.resetSearchProviderForTests(); - mockAdvancedSearchManager.mockClear(); mockBasicSearchManager.mockClear(); mockSearchReader.mockClear(); mockClientSearch.mockReset(); @@ -71,20 +88,6 @@ describe('searchProvider', () => { expect(mockGetDefaultSearchProviderSpec).toHaveBeenCalledTimes(1); }); - test('reuses one advanced search manager per process', () => { - const first = searchProvider.getAdvancedSearchManager(); - const second = searchProvider.getAdvancedSearchManager(); - - expect(first).toBe(second); - expect(mockAdvancedSearchManager).toHaveBeenCalledTimes(1); - expect(mockAdvancedSearchManager).toHaveBeenCalledWith({ - providerEndpoint: 'https://search.example', - index: 'test-index', - allowUnknownSslCerts: false - }); - expect(mockGetDefaultSearchProviderSpec).toHaveBeenCalledTimes(1); - }); - test('reuses one basic search manager per process', () => { const first = searchProvider.getBasicSearchManager(); const second = searchProvider.getBasicSearchManager(); @@ -97,9 +100,11 @@ describe('searchProvider', () => { allowUnknownSslCerts: false }); expect(mockGetDefaultSearchProviderSpec).toHaveBeenCalledTimes(1); + expect(first._searchReader._client.transport.maxRetries).toBe(0); + expect(first._searchReader._client.transport.requestTimeout).toBe(20000); }); - test('reuses one search reader per process for raw OpenSearch searches', async () => { + test('reuses one search reader per process for OpenSearch searches', async () => { mockClientSearch.mockResolvedValue({ body: { hits: { @@ -118,17 +123,29 @@ describe('searchProvider', () => { match_all: {} } }; - const result = await searchProvider.searchRaw(body); + const onStart = jest.fn(); + const result = await searchProvider.searchOpenSearch(body, { + operation: 'standard', + onStart + }); expect(first).toBe(second); expect(mockSearchReader).toHaveBeenCalledTimes(1); expect(mockSearchReader).toHaveBeenCalledWith('https://search.example', 'test-index', { ignore_ssl_cert_check_errors: false }); - expect(mockClientSearch).toHaveBeenCalledWith({ - index: 'test-index', - body - }); + expect(mockClientSearch).toHaveBeenCalledWith( + { + index: 'test-index', + body + }, + { + maxRetries: 0, + requestTimeout: 20000 + } + ); + expect(first._client.transport.maxRetries).toBe(0); + expect(first._client.transport.requestTimeout).toBe(20000); expect(result).toStrictEqual({ hits: { total: { @@ -137,6 +154,207 @@ describe('searchProvider', () => { hits: [] } }); + expect(onStart).toHaveBeenCalledWith({ + activeCount: 1, + activeByOperation: { + standard: 1 + } + }); + expect(searchProvider.getOpenSearchRequestState()).toStrictEqual({ + activeCount: 0, + activeByOperation: {} + }); + }); + + test('checks OpenSearch readiness through the configured CVE index', async () => { + mockClientSearch.mockResolvedValue({ + body: { + hits: { + hits: [] + } + } + }); + + await expect(searchProvider.checkOpenSearchReadiness()).resolves.toBe(true); + + expect(mockClientSearch).toHaveBeenCalledWith( + { + index: 'test-index', + body: { + query: { + match_all: {} + }, + size: 0, + terminate_after: 1, + track_total_hits: false + } + }, + { + maxRetries: 0, + requestTimeout: 5000 + } + ); + expect(searchProvider.getOpenSearchRequestState()).toStrictEqual({ + activeCount: 0, + activeByOperation: {} + }); + }); + + test('fails readiness when the configured CVE index is unavailable', async () => { + const unavailable = new Error('index unavailable'); + mockClientSearch.mockRejectedValue(unavailable); + + await expect(searchProvider.checkOpenSearchReadiness()).rejects.toBe(unavailable); + }); + + test('aborts the readiness transport request when its lifecycle is canceled', async () => { + const pending = deferred(); + const request = pending.promise; + request.abort = jest.fn(); + mockClientSearch.mockReturnValue(request); + const controller = new AbortController(); + const readiness = searchProvider.checkOpenSearchReadiness({ + signal: controller.signal + }); + await Promise.resolve(); + + controller.abort(new searchDeadline.SearchDeadlineExceededError()); + + await expect(readiness).rejects.toMatchObject({ + code: 'SEARCH_TIMEOUT' + }); + expect(request.abort).toHaveBeenCalledTimes(1); + expect(searchProvider.getOpenSearchRequestState()).toStrictEqual({ + activeCount: 0, + activeByOperation: {} + }); + }); + + test('does not construct a SearchReader for an already-canceled readiness check', async () => { + const controller = new AbortController(); + controller.abort(new searchDeadline.SearchClientDisconnectedError()); + + await expect( + searchProvider.checkOpenSearchReadiness({ + signal: controller.signal + }) + ).rejects.toMatchObject({ + code: 'SEARCH_CLIENT_DISCONNECTED' + }); + expect(mockSearchReader).not.toHaveBeenCalled(); + expect(mockClientSearch).not.toHaveBeenCalled(); + }); + + test('rejects and does not cache an incompatible cve-core SearchReader', () => { + mockSearchReader.mockImplementationOnce(() => ({ + _cveIndex: 'test-index', + _client: { + transport: {} + } + })); + + expect(() => searchProvider.getSearchReader()).toThrow(searchProvider.SearchProviderContractError); + expect(searchProvider.getSearchReader()).toBeDefined(); + expect(mockSearchReader).toHaveBeenCalledTimes(2); + }); + + test('aborts an active OpenSearch request when the lifecycle signal aborts', async () => { + const pending = deferred(); + const request = pending.promise; + request.abort = jest.fn(); + mockClientSearch.mockReturnValue(request); + const controller = new AbortController(); + const search = searchProvider.searchOpenSearch( + { + query: { + match_all: {} + } + }, + { + signal: controller.signal, + operation: 'standard' + } + ); + await Promise.resolve(); + + controller.abort(new searchDeadline.SearchDeadlineExceededError()); + + await expect(search).rejects.toMatchObject({ + code: 'SEARCH_TIMEOUT' + }); + expect(request.abort).toHaveBeenCalledTimes(1); + expect(searchProvider.getOpenSearchRequestState()).toStrictEqual({ + activeCount: 0, + activeByOperation: {} + }); + }); + + test('tracks concurrent OpenSearch requests by operation type', async () => { + const candidate = deferred(); + const pageFetch = deferred(); + const candidateStart = jest.fn(); + const pageFetchStart = jest.fn(); + mockClientSearch.mockReturnValueOnce(candidate.promise).mockReturnValueOnce(pageFetch.promise); + + const candidateSearch = searchProvider.searchOpenSearch( + {}, + { + operation: 'cpe-candidate', + onStart: candidateStart + } + ); + const pageSearch = searchProvider.searchOpenSearch( + {}, + { + operation: 'page-fetch', + onStart: pageFetchStart + } + ); + + expect(candidateStart).toHaveBeenCalledWith({ + activeCount: 1, + activeByOperation: { + 'cpe-candidate': 1 + } + }); + expect(pageFetchStart).toHaveBeenCalledWith({ + activeCount: 2, + activeByOperation: { + 'cpe-candidate': 1, + 'page-fetch': 1 + } + }); + + candidate.resolve({ body: { hits: { hits: [] } } }); + pageFetch.resolve({ body: { hits: { hits: [] } } }); + await Promise.all([candidateSearch, pageSearch]); + + expect(searchProvider.getOpenSearchRequestState()).toStrictEqual({ + activeCount: 0, + activeByOperation: {} + }); + }); + + test('does not start an OpenSearch request for an already-aborted signal', async () => { + const controller = new AbortController(); + controller.abort(new searchDeadline.SearchClientDisconnectedError()); + + await expect( + searchProvider.searchOpenSearch( + { + query: { + match_all: {} + } + }, + { + signal: controller.signal + } + ) + ).rejects.toMatchObject({ + code: 'SEARCH_CLIENT_DISCONNECTED' + }); + expect(mockSearchReader).not.toHaveBeenCalled(); + expect(mockClientSearch).not.toHaveBeenCalled(); }); test('throws when OpenSearch endpoint or index are missing', () => { @@ -187,7 +405,11 @@ describe('searchProvider', () => { 'search.postFilterCacheTtlMs', 'search.postFilterCacheMaxEntries', 'search.postFilterColdSearchConcurrency', - 'search.postFilterColdSearchMaxQueueDepth' + 'search.postFilterColdSearchMaxQueueDepth', + 'search.postFilterColdSearchMaxWaitMs', + 'search.openSearchRequestTimeoutMs', + 'search.requestDeadlineMs', + 'search.readinessTimeoutMs' ].includes(key) ); mockAppConfigGet.mockImplementation((key) => { @@ -195,7 +417,11 @@ describe('searchProvider', () => { 'search.postFilterCacheTtlMs': '120000', 'search.postFilterCacheMaxEntries': '250', 'search.postFilterColdSearchConcurrency': '6', - 'search.postFilterColdSearchMaxQueueDepth': '50' + 'search.postFilterColdSearchMaxQueueDepth': '50', + 'search.postFilterColdSearchMaxWaitMs': '15000', + 'search.openSearchRequestTimeoutMs': '25000', + 'search.requestDeadlineMs': '45000', + 'search.readinessTimeoutMs': '7000' }; return values[key]; }); @@ -204,6 +430,10 @@ describe('searchProvider', () => { expect(searchProvider.getPostFilterCacheMaxEntries()).toBe(250); expect(searchProvider.getPostFilterColdSearchConcurrency()).toBe(6); expect(searchProvider.getPostFilterColdSearchMaxQueueDepth()).toBe(50); + expect(searchProvider.getPostFilterColdSearchMaxWaitMs()).toBe(15000); + expect(searchProvider.getOpenSearchRequestTimeoutMs()).toBe(25000); + expect(searchProvider.getSearchRequestDeadlineMs()).toBe(45000); + expect(searchProvider.getOpenSearchReadinessTimeoutMs()).toBe(7000); }); test('falls back to CPE post-filter runtime defaults when config values are invalid', () => { @@ -214,5 +444,9 @@ describe('searchProvider', () => { expect(searchProvider.getPostFilterCacheMaxEntries()).toBe(100); expect(searchProvider.getPostFilterColdSearchConcurrency()).toBe(10); expect(searchProvider.getPostFilterColdSearchMaxQueueDepth()).toBe(500); + expect(searchProvider.getPostFilterColdSearchMaxWaitMs()).toBe(10000); + expect(searchProvider.getOpenSearchRequestTimeoutMs()).toBe(20000); + expect(searchProvider.getSearchRequestDeadlineMs()).toBe(30000); + expect(searchProvider.getOpenSearchReadinessTimeoutMs()).toBe(5000); }); }); diff --git a/utils/searchQueryBuilder.js b/utils/searchQueryBuilder.js index b2e1534..07894bb 100644 --- a/utils/searchQueryBuilder.js +++ b/utils/searchQueryBuilder.js @@ -10,6 +10,7 @@ const modDatePath = 'cveMetadata.dateUpdated'; const cpeCandidateSourceFields = [ 'cveMetadata.cveId', 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria', + 'containers.cna.cpeApplicability.nodes.cpeMatch.vulnerable', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionStartIncluding', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionStartExcluding', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionEndIncluding', @@ -97,6 +98,7 @@ function buildPostFilters(params) { const virtualMatchStringRangePostFilter = getVirtualMatchStringRangePostFilter(params); if (cpeNamePostFilter) postFilters.cpeName = cpeNamePostFilter; + if (cpeNamePostFilter && isTrueValue(params?.isVulnerable)) postFilters.isVulnerable = true; if (virtualMatchStringRangePostFilter) postFilters.virtualMatchStringRange = virtualMatchStringRangePostFilter; else if (virtualMatchStringPostFilter) postFilters.virtualMatchString = virtualMatchStringPostFilter; @@ -204,6 +206,10 @@ function processParams(params) { return allParams; } +function isTrueValue(value) { + return value === true || String(value).toLowerCase() === 'true'; +} + function addWildCard(searchString) { const terms = String(searchString ?? '') .trim() @@ -293,6 +299,17 @@ function getFilterFields(params) { return; } + if (param === 'isVulnerable') { + if (isTrueValue(params[param])) { + fieldsToReturn.push({ + term: { + [fieldSpec]: true + } + }); + } + return; + } + if (param === 'virtualMatchString') { const filter = buildVirtualMatchStringFilter(params[param]); if (filter) fieldsToReturn.push(filter); @@ -432,7 +449,8 @@ function buildCpeCriteriaPrefixFilter(field, prefixes) { const prefixFilters = prefixes.map((value) => ({ prefix: { [field]: { - value + value, + case_insensitive: true } } })); @@ -452,7 +470,8 @@ function buildCpeCriteriaRegexpFilter(field, value) { return { regexp: { [field]: { - value + value, + case_insensitive: true } } }; diff --git a/utils/searchQueryBuilder.test.js b/utils/searchQueryBuilder.test.js index 4f09cfd..b26f879 100644 --- a/utils/searchQueryBuilder.test.js +++ b/utils/searchQueryBuilder.test.js @@ -4,7 +4,8 @@ function prefixFilter(value) { return { prefix: { 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria.keyword': { - value + value, + case_insensitive: true } } }; @@ -90,6 +91,36 @@ describe('searchQueryBuilder buildSearchRequest()', () => { expect(output.postFilters.cpeName.version).toBe('2.10'); }); + test('buildSearchRequest preserves true isVulnerable for same-entry semantic filtering', () => { + const output = queryBuilder.buildSearchRequest({ + cpeName: 'cpe:2.3:a:vendor:product:2.10:*:*:*:*:*:*:*', + isVulnerable: true + }); + + expect(output.postFilters.isVulnerable).toBe(true); + expect(output.searchOptions.filters).toContainEqual({ + term: { + 'containers.cna.cpeApplicability.nodes.cpeMatch.vulnerable': true + } + }); + }); + + test('buildSearchRequest treats false isVulnerable as no restriction', () => { + const output = queryBuilder.buildSearchRequest({ + cpeName: 'cpe:2.3:a:vendor:product:2.10:*:*:*:*:*:*:*', + isVulnerable: false + }); + + expect(output.postFilters.isVulnerable).toBeUndefined(); + expect(output.searchOptions.filters).not.toContainEqual( + expect.objectContaining({ + term: expect.objectContaining({ + 'containers.cna.cpeApplicability.nodes.cpeMatch.vulnerable': expect.anything() + }) + }) + ); + }); + test('buildSearchRequest over-fetches virtualMatchString candidates for semantic post-filtering', () => { const output = queryBuilder.buildSearchRequest({ virtualMatchString: 'cpe:2.3:o:microsoft:windows_10_1809:*:*:*:*:*:*:x86:*', @@ -108,7 +139,8 @@ describe('searchQueryBuilder buildSearchRequest()', () => { { regexp: { 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria.keyword': { - value: 'cpe:2\\.3:(o|\\*):(microsoft|\\*):(windows_10_1809|\\*):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:(x86|\\*):[^:]*' + value: 'cpe:2\\.3:(o|\\*):(microsoft|\\*):(windows_10_1809|\\*):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:(x86|\\*):[^:]*', + case_insensitive: true } } } @@ -189,6 +221,7 @@ describe('searchQueryBuilder buildSearchRequest()', () => { _source: [ 'cveMetadata.cveId', 'containers.cna.cpeApplicability.nodes.cpeMatch.criteria', + 'containers.cna.cpeApplicability.nodes.cpeMatch.vulnerable', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionStartIncluding', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionStartExcluding', 'containers.cna.cpeApplicability.nodes.cpeMatch.versionEndIncluding', @@ -198,6 +231,41 @@ describe('searchQueryBuilder buildSearchRequest()', () => { ); }); + test('buildOpenSearchBody builds the complete standard search request', () => { + const output = queryBuilder.buildSearchRequest({ + keywordSearch: 'remote code', + cweId: 'CWE-79', + resultsPerPage: 10, + pageNumber: 3 + }); + + expect(queryBuilder.buildOpenSearchBody(output.searchOptions, output.queryStringObjects)).toStrictEqual({ + query: { + bool: { + must: [ + { + simple_query_string: { + query: 'remote* code*', + default_operator: 'AND', + fields: ['containers.cna.descriptions.value'] + } + } + ], + filter: [ + { + match_phrase: { + 'containers.cna.problemTypes.descriptions.cweId': 'CWE-79' + } + } + ] + } + }, + track_total_hits: true, + size: 10, + from: 20 + }); + }); + test('buildCveIdsSearchRequest builds exact page fetch request for selected CVE IDs', () => { expect(queryBuilder.buildCveIdsSearchRequest(['CVE-2024-0002', 'CVE-2024-0001'])).toStrictEqual({ searchOptions: { @@ -238,34 +306,6 @@ describe('searchQueryBuilder buildQueryStrings() function', () => { expect(output).toStrictEqual(testQueryString); }); - test('buildQueryStrings() function correctly builds mixed query objects when multiple query string parameters are passed.', () => { - const testParams = { - keywordSearch: 'test* params*', - sourceIdentifier: 'EXTERNAL' - }; - - const testQueryString = [ - { - simple_query_string: { - query: 'test* params*', - default_operator: 'AND', - fields: ['containers.cna.descriptions.value'] - } - }, - { - query_string: { - query: 'EXTERNAL', - default_operator: 'AND', - fields: ['containers.cna.source.discovery'] - } - } - ]; - - const output = queryBuilder.buildQueryStrings(testParams); - expect(output).toHaveLength(2); - expect(output).toStrictEqual(testQueryString); - }); - test('buildQueryStrings() ignores virtualMatchString because it is handled as a structured filter.', () => { const output = queryBuilder.buildQueryStrings({ virtualMatchString: 'cpe:2.3:o:microsoft:windows_10_1809' @@ -278,13 +318,13 @@ describe('searchQueryBuilder buildQueryStrings() function', () => { describe('searchQueryBuilder getFilterFields() function', () => { test('getFilterFields() function correctly builds array of one matchPhrase object when one parameter is passed.', () => { const testParams = { - sourceIdentifier: 'EXTERNAL' + cweId: 'CWE-79' }; const testMatchPhrase = [ { match_phrase: { - 'containers.cna.source.discovery': 'EXTERNAL' + 'containers.cna.problemTypes.descriptions.cweId': 'CWE-79' } } ]; @@ -296,14 +336,14 @@ describe('searchQueryBuilder getFilterFields() function', () => { test('getFilterFields() function correctly builds default and severity filter objects when multiple parameters are passed.', () => { const testParams = { - sourceIdentifier: 'EXTERNAL', + cweId: 'CWE-79', cvssV4Severity: 'HIGH' }; const testMatchPhrase = [ { match_phrase: { - 'containers.cna.source.discovery': 'EXTERNAL' + 'containers.cna.problemTypes.descriptions.cweId': 'CWE-79' } }, { diff --git a/utils/searchRateLimiter.js b/utils/searchRateLimiter.js new file mode 100644 index 0000000..00026e1 --- /dev/null +++ b/utils/searchRateLimiter.js @@ -0,0 +1,100 @@ +const { AppConfig } = require('cve-core'); +const { rateLimit } = require('express-rate-limit'); +const errors = require('./errors'); +const searchPerformance = require('./searchPerformance'); + +const DEFAULT_SEARCH_RATE_LIMIT_ENABLED = false; +const DEFAULT_SEARCH_RATE_LIMIT_WINDOW_MS = 60000; +const GLOBAL_SEARCH_RATE_LIMIT_KEY = 'search-api-process'; + +const error = new errors.errors(); + +function parseBoolean(value) { + if (typeof value === 'boolean') return value; + + const normalized = String(value).trim().toLowerCase(); + if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) return true; + if (['false', '0', 'no', 'n', 'off'].includes(normalized)) return false; + + return undefined; +} + +function readBooleanConfig(path, defaultValue) { + if (!AppConfig.has(path)) return defaultValue; + + const value = parseBoolean(AppConfig.get(path)); + if (value === undefined) throw new Error(`${path} must be a boolean.`); + + return value; +} + +function readPositiveIntegerConfig(path, defaultValue) { + if (!AppConfig.has(path)) return defaultValue; + + const value = Number(AppConfig.get(path)); + if (!Number.isInteger(value) || value < 1) return undefined; + + return value; +} + +function getSearchRateLimitConfig() { + const enabled = readBooleanConfig('rateLimiting.search.enabled', DEFAULT_SEARCH_RATE_LIMIT_ENABLED); + if (!enabled) { + return Object.freeze({ + enabled, + windowMs: DEFAULT_SEARCH_RATE_LIMIT_WINDOW_MS + }); + } + + const windowMs = readPositiveIntegerConfig('rateLimiting.search.windowMs', DEFAULT_SEARCH_RATE_LIMIT_WINDOW_MS); + const maxRequests = readPositiveIntegerConfig('rateLimiting.search.maxRequests'); + + if (!windowMs) throw new Error('rateLimiting.search.windowMs must be a positive integer when enabled.'); + if (!maxRequests) throw new Error('rateLimiting.search.maxRequests must be a positive integer when enabled.'); + + return Object.freeze({ + enabled, + windowMs, + maxRequests + }); +} + +function disabledRateLimiter(_req, _res, next) { + next(); +} + +function handleRateLimit(req, res, _next, options) { + searchPerformance.logSearchPerformance( + { + requestId: req.requestId, + outcome: 'rate_limited', + statusCode: options.statusCode + }, + undefined, + undefined, + undefined + ); + + return res.status(options.statusCode).json(error.searchRateLimitedError()); +} + +function createSearchRateLimiter(config = getSearchRateLimitConfig()) { + if (!config.enabled) return disabledRateLimiter; + + return rateLimit({ + windowMs: config.windowMs, + limit: config.maxRequests, + standardHeaders: 'draft-8', + legacyHeaders: false, + identifier: GLOBAL_SEARCH_RATE_LIMIT_KEY, + keyGenerator: () => GLOBAL_SEARCH_RATE_LIMIT_KEY, + handler: handleRateLimit, + passOnStoreError: false + }); +} + +module.exports = { + createSearchRateLimiter, + getSearchRateLimitConfig, + handleRateLimit +}; diff --git a/utils/searchRateLimiter.test.js b/utils/searchRateLimiter.test.js new file mode 100644 index 0000000..da00ea6 --- /dev/null +++ b/utils/searchRateLimiter.test.js @@ -0,0 +1,127 @@ +const express = require('express'); +const { request } = require('../test/http'); + +const mockAppConfigHas = jest.fn(); +const mockAppConfigGet = jest.fn(); +const mockLogSearchPerformance = jest.fn(); + +jest.mock('cve-core', () => ({ + AppConfig: { + get: mockAppConfigGet, + has: mockAppConfigHas + } +})); + +jest.mock('./searchPerformance', () => ({ + logSearchPerformance: mockLogSearchPerformance +})); + +const searchRateLimiter = require('./searchRateLimiter'); + +describe('searchRateLimiter', () => { + beforeEach(() => { + mockAppConfigHas.mockReset(); + mockAppConfigGet.mockReset(); + mockLogSearchPerformance.mockReset(); + mockAppConfigHas.mockReturnValue(false); + }); + + test('defaults to a disabled limiter that passes requests through', () => { + const next = jest.fn(); + const limiter = searchRateLimiter.createSearchRateLimiter(); + + limiter({}, {}, next); + + expect(next).toHaveBeenCalledTimes(1); + }); + + test('resolves enabled rate limit configuration from AppConfig', () => { + const values = { + 'rateLimiting.search.enabled': 'TRUE', + 'rateLimiting.search.windowMs': '30000', + 'rateLimiting.search.maxRequests': '25' + }; + mockAppConfigHas.mockImplementation((path) => Object.hasOwn(values, path)); + mockAppConfigGet.mockImplementation((path) => values[path]); + + expect(searchRateLimiter.getSearchRateLimitConfig()).toStrictEqual({ + enabled: true, + windowMs: 30000, + maxRequests: 25 + }); + }); + + test.each([ + [ + { + 'rateLimiting.search.enabled': 'invalid' + }, + 'rateLimiting.search.enabled must be a boolean.' + ], + [ + { + 'rateLimiting.search.enabled': 'TRUE', + 'rateLimiting.search.windowMs': '0', + 'rateLimiting.search.maxRequests': '25' + }, + 'rateLimiting.search.windowMs must be a positive integer when enabled.' + ], + [ + { + 'rateLimiting.search.enabled': 'TRUE', + 'rateLimiting.search.windowMs': '60000', + 'rateLimiting.search.maxRequests': '0' + }, + 'rateLimiting.search.maxRequests must be a positive integer when enabled.' + ] + ])('rejects invalid explicit configuration', (values, expectedMessage) => { + mockAppConfigHas.mockImplementation((path) => Object.hasOwn(values, path)); + mockAppConfigGet.mockImplementation((path) => values[path]); + + expect(() => searchRateLimiter.getSearchRateLimitConfig()).toThrow(expectedMessage); + }); + + test('enforces a process-wide limit with standard headers and sanitized telemetry', async () => { + const app = express(); + const limiter = searchRateLimiter.createSearchRateLimiter({ + enabled: true, + windowMs: 60000, + maxRequests: 1 + }); + + app.use((req, res, next) => { + req.requestId = 'request-123'; + next(); + }); + app.post('/search', limiter, (req, res) => res.status(200).json({ ok: true })); + + const accepted = await request(app, { + method: 'POST', + path: '/search' + }); + const rejected = await request(app, { + method: 'POST', + path: '/search' + }); + + expect(accepted.status).toBe(200); + expect(accepted.headers.ratelimit).toBeDefined(); + expect(accepted.headers['ratelimit-policy']).toBeDefined(); + expect(rejected.status).toBe(429); + expect(rejected.headers['retry-after']).toBeDefined(); + expect(rejected.body).toStrictEqual({ + error: 'SEARCH_RATE_LIMITED', + message: 'Too many search requests. Please retry later.' + }); + expect(mockLogSearchPerformance).toHaveBeenCalledWith( + { + requestId: 'request-123', + outcome: 'rate_limited', + statusCode: 429 + }, + undefined, + undefined, + undefined + ); + }); +});